""" ═══════════════════════════════════════════════════════════════════════════════ HTDN → Hugging Face Hub: تحويل نقطة تفتيش Orbax ورفعها إلى مستودع HF (نسخة محدَّثة: التحميل والتشغيل بعد الرفع أصبحا تلقائيَّين بالكامل) ═══════════════════════════════════════════════════════════════════════════════ ⚠️ طريقة الاستخدام على Kaggle: 1) Settings → Internet → On (لازم للرفع الفعلي إلى huggingface.co) 2) Add-ons → Secrets → أضف HF_TOKEN (توكن Hugging Face بصلاحية "Write"). 3) عدّل HF_REPO_ID أسفل (اسم المستخدم/اسم المستودع الذي تريده على HF). 4) شغّل هذا الملف في نفس النوتبوك الذي حوى التدريب (يقرأ مباشرة من /kaggle/working)، أو في نوتبوك جديد بعد إرفاق مخرجات جلسة التدريب (orbax_checkpoints + training_meta.json + htdn_kaggle_t4.py) كـ Input (Notebook Output Files أو Model) — الكود يبحث تلقائياً في كليهما. %%writefile htdn_export_to_hf.py [الصق هذا الملف] ثم في خلية تالية: !python htdn_export_to_hf.py ═══════════════════════════════════════════════════════════════════════════ ماذا يفعل هذا السكربت بالضبط؟ ═══════════════════════════════════════════════════════════════════════════ 1. يبحث عن آخر نقطة تفتيش Orbax محفوظة (نفس منطق try_resume في سكربت التدريب) — محلياً أولاً (/kaggle/working) ثم في المرفقات (/kaggle/input). 2. يعيد بناء نفس بنية النموذج (HTDN) بالقياسات المحفوظة في training_meta.json، لإنشاء "قالب" (template) تُستعاد فيه المعاملات. 3. يستعيد المعاملات عبر Orbax (StandardRestore). 4. يحوّل شجرة المعاملات (params pytree) إلى ملف واحد safetensors — الصيغة المعيارية والآمنة على Hugging Face (لا صيغة pickle). 5. يحفظ المحلل اللغوي (tokenizer) وملف config.json وبطاقة نموذج README.md، بالإضافة إلى نسخة من كود النموذج (لإعادة التحميل لاحقاً عبر JAX/Flax، لأن هذه بنية مخصّصة وليست معمارية transformers قياسية يفهمها AutoModel مباشرة). 6. يرفع كل شيء إلى مستودع Hugging Face المحدَّد في HF_REPO_ID. 7. 🆕 يشغّل اختباراً تلقائياً بعد الرفع مباشرةً: يُنزّل ما رفعه للتو ويولّد جملة عيّنة، للتأكد أن كل شيء (بما فيه الاستخدام لاحقاً) يعمل تلقائياً من أول محاولة، دون أي تدخّل يدوي. ═══════════════════════════════════════════════════════════════════════════ 🆕 كيف صار استخدام النموذج بعد الرفع (بدل الطريقة اليدوية القديمة)؟ ═══════════════════════════════════════════════════════════════════════════ بدل تنزيل الملفات يدوياً وتفكيك/تجميع شجرة المعاملات بنفسك، صار كل شيء داخل دالتين جاهزتين مرفوعتين ضمن modeling_htdn.py في المستودع نفسه: from huggingface_hub import hf_hub_download import importlib.util path = hf_hub_download("gijl/htdn-arabic-350m1", "modeling_htdn.py") spec = importlib.util.spec_from_file_location("modeling_htdn", path) modeling_htdn = importlib.util.module_from_spec(spec) spec.loader.exec_module(modeling_htdn) # تحميل تلقائي كامل: النموذج + الأوزان + المحلل اللغوي model, params, tokenizer, config = modeling_htdn.load_htdn_from_hub( "gijl/htdn-arabic-350m1" ) # توليد نص تلقائي كامل print(modeling_htdn.generate(model, params, tokenizer, "مرحبا")) # أو الاختصار الكامل بسطر واحد فعلي: # print(modeling_htdn.quick_generate("gijl/htdn-arabic-350m1", "مرحبا")) (خطوة تنزيل modeling_htdn.py نفسها لا مفرّ منها لأن هذه بنية مخصّصة وليست على PyPI — لكنها خطوة واحدة فقط، وكل ما بعدها تلقائي بالكامل.) ═══════════════════════════════════════════════════════════════════════════ """ import os import sys import subprocess # ════════════════════════════════════════════════════════════════════════════════ # 0. إعدادات قابلة للتعديل — عدّل هذا القسم فقط عادةً # ════════════════════════════════════════════════════════════════════════════════ HF_REPO_ID = "gijl/htdn-arabic-350m1" # ⚠️ عدّل هذا لاسم مستودعك على HF PRIVATE_REPO = False # True لو تريد مستودعاً خاصاً COMMIT_MSG = "Upload HTDN checkpoint" RUN_SELF_TEST_AFTER_UPLOAD = True # 🆕 يجرّب تحميل/تشغيل النموذج تلقائياً بعد الرفع DEMO_PROMPT = "مرحبا، كيف حالك؟" # 🆕 الجملة المستخدَمة في الاختبار التلقائي DEMO_MAX_NEW_TOKENS = 30 # ════════════════════════════════════════════════════════════════════════════════ # البذرة الذاتية: تثبيت المكتبات الناقصة # ════════════════════════════════════════════════════════════════════════════════ def _bootstrap_install(): missing = [] for mod, pip_name in [ ("jax", "jax[cuda12]"), ("flax", "flax"), ("orbax.checkpoint", "orbax-checkpoint"), ("transformers", "transformers"), ("huggingface_hub", "huggingface_hub"), ("safetensors", "safetensors"), ]: try: __import__(mod) except Exception: missing.append(pip_name) if missing: print(f"📦 تثبيت المكتبات الناقصة: {missing} ...", flush=True) subprocess.run([sys.executable, "-m", "pip", "install", "-q", "-U"] + missing, check=True) _bootstrap_install() os.environ["USE_TORCH"] = "0" os.environ["USE_TF"] = "0" os.environ["USE_FLAX"] = "0" import glob import json import shutil import numpy as np import jax import jax.numpy as jnp import flax.linen as nn from flax.traverse_util import flatten_dict, unflatten_dict import orbax.checkpoint as ocp from transformers import AutoTokenizer from safetensors.numpy import save_file from safetensors import safe_open from huggingface_hub import HfApi, create_repo, upload_folder, hf_hub_download KAGGLE_INPUT = "/kaggle/input" KAGGLE_OUTPUT = "/kaggle/working" LOCAL_EXPORT_DIR = os.path.join(KAGGLE_OUTPUT if os.path.isdir(KAGGLE_OUTPUT) else ".", "hf_export") os.makedirs(LOCAL_EXPORT_DIR, exist_ok=True) def log(msg): print(msg, flush=True) # ════════════════════════════════════════════════════════════════════════════════ # 1. تسجيل الدخول إلى Hugging Face # ════════════════════════════════════════════════════════════════════════════════ def login_hf(): token = None try: from kaggle_secrets import UserSecretsClient token = UserSecretsClient().get_secret("HF_TOKEN") except Exception: token = os.environ.get("HF_TOKEN") if not token: raise RuntimeError( "لم يُعثر على HF_TOKEN. أضفه في Kaggle: Add-ons → Secrets → HF_TOKEN " "(توكن من https://huggingface.co/settings/tokens بصلاحية Write)." ) from huggingface_hub import login login(token=token, add_to_git_credential=False) log("🔑 تم تسجيل الدخول إلى Hugging Face Hub") return token # ════════════════════════════════════════════════════════════════════════════════ # 2. تحديد موقع آخر نقطة تفتيش + ملف الميتاداتا # ════════════════════════════════════════════════════════════════════════════════ def find_checkpoint_source(): candidates = [] for base in (KAGGLE_OUTPUT, KAGGLE_INPUT): if os.path.isdir(base): candidates += glob.glob(os.path.join(base, "**", "orbax_checkpoints"), recursive=True) candidates = [c for c in candidates if os.path.isdir(c)] if not candidates: raise RuntimeError( "لم يُعثر على أي مجلد orbax_checkpoints — تأكد أنك تشغّل هذا في نفس " "نوتبوك التدريب (/kaggle/working) أو أرفقت مخرجات جلسة التدريب كـ Input." ) candidates.sort(key=lambda p: os.path.getmtime(p), reverse=True) ckpt_root = candidates[0] steps = sorted(int(n) for n in os.listdir(ckpt_root) if n.isdigit()) if not steps: raise RuntimeError(f"المجلد {ckpt_root} لا يحوي أي خطوة checkpoint صالحة") latest_step = steps[-1] meta_path = os.path.join(os.path.dirname(ckpt_root), "training_meta.json") if not os.path.exists(meta_path): # قد يكون meta ضمن مسار مختلف قليلاً (مثلاً مرفق Model)؛ نبحث حوله alt = glob.glob(os.path.join(os.path.dirname(ckpt_root), "**", "training_meta.json"), recursive=True) meta_path = alt[0] if alt else None log(f"📂 مجلد نقاط التفتيش: {ckpt_root}") log(f"🔢 آخر خطوة: {latest_step:,}") log(f"📄 ملف الميتاداتا: {meta_path or 'غير موجود — سيُستخدم إعدادات افتراضية'}") return ckpt_root, latest_step, meta_path def get_writable_checkpoint_root(ckpt_root: str, step: int) -> str: """CheckpointManager يحتاج مجلداً قابلاً للكتابة. لو نقطة التفتيش تحت /kaggle/input (للقراءة فقط)، نسخ خطوة الاستعادة فقط (لا كل الخطوات) إلى مكان محلي قابل للكتابة.""" if ckpt_root.startswith(KAGGLE_OUTPUT) and os.access(ckpt_root, os.W_OK): return ckpt_root local_root = os.path.join(LOCAL_EXPORT_DIR, "_ckpt_copy") dst_step_dir = os.path.join(local_root, str(step)) if not os.path.isdir(dst_step_dir): os.makedirs(local_root, exist_ok=True) log(" 📋 نسخ نقطة التفتيش إلى مجلد محلي قابل للكتابة (المصدر للقراءة فقط)...") shutil.copytree(os.path.join(ckpt_root, str(step)), dst_step_dir) log(f" ✅ اكتمل النسخ → {dst_step_dir}") return local_root # ════════════════════════════════════════════════════════════════════════════════ # 3. إعادة بناء بنية النموذج (نفس بنية التدريب) — لازمة كقالب استعادة # فقط. COMPUTE_DTYPE هنا float32 دوماً (نسخة الاستيراد لا التدريب)، # لأن معاملات Flax تُخزَّن بدقة float32 افتراضياً بصرف النظر عن dtype # الحساب المستخدَم أثناء التدريب على GPU/TPU (dtype يتحكم فقط بحساب # forward، لا بدقة تخزين الأوزان). # ════════════════════════════════════════════════════════════════════════════════ COMPUTE_DTYPE = jnp.float32 def rope_frequencies(d_head, base=10000.0): inv_freq = 1.0 / (base ** (jnp.arange(0, d_head, 2, dtype=jnp.float32) / d_head)) return inv_freq def apply_rope(x, positions, inv_freq): freqs = jnp.einsum("l,d->ld", positions.astype(jnp.float32), inv_freq) cos = jnp.cos(freqs)[None, None, :, :] sin = jnp.sin(freqs)[None, None, :, :] x1, x2 = jnp.split(x.astype(jnp.float32), 2, axis=-1) rotated = jnp.concatenate([x1 * cos - x2 * sin, x2 * cos + x1 * sin], axis=-1) return rotated.astype(x.dtype) def _linear_recurrence_combine(l, r): a_l, b_l = l a_r, b_r = r return a_l * a_r, a_r * b_l + b_r class DynamicsLayer(nn.Module): d_u: int d_v: int n_attractors: int num_heads: int @nn.compact def __call__(self, x, steps): u = nn.Dense(self.d_u, dtype=COMPUTE_DTYPE, name="proj_u")(x) v = nn.Dense(self.d_v, dtype=COMPUTE_DTYPE, name="proj_v")(x) log_Du = self.param("log_Du", nn.initializers.normal(stddev=0.01), (self.num_heads,)) log_Dv = self.param("log_Dv", nn.initializers.normal(stddev=0.01), (self.num_heads,)) Du = nn.softplus(log_Du).mean() Dv = nn.softplus(log_Dv).mean() attr_u = nn.Dense(self.n_attractors, dtype=COMPUTE_DTYPE, name="attr_u")(u) attr_v = nn.Dense(self.n_attractors, dtype=COMPUTE_DTYPE, name="attr_v")(v) back_u = nn.Dense(self.d_u, dtype=COMPUTE_DTYPE, name="back_u")(attr_u) back_v = nn.Dense(self.d_v, dtype=COMPUTE_DTYPE, name="back_v")(attr_v) SAFE_BOUND = 30.0 u0 = jnp.clip(u.astype(jnp.float32), -SAFE_BOUND, SAFE_BOUND) v0 = jnp.clip(v.astype(jnp.float32), -SAFE_BOUND, SAFE_BOUND) back_u = jnp.clip(back_u.astype(jnp.float32), -SAFE_BOUND, SAFE_BOUND) back_v = jnp.clip(back_v.astype(jnp.float32), -SAFE_BOUND, SAFE_BOUND) Du32 = Du.astype(jnp.float32) Dv32 = Dv.astype(jnp.float32) dt = 0.1 / max(1, steps) a_u = jnp.clip(1.0 - dt * Du32, -0.99, 1.0) a_v = jnp.clip(1.0 - dt * Dv32, -0.99, 1.0) b_u = dt * back_u b_v = dt * back_v A_u_seq = jnp.broadcast_to(a_u, (steps,) + u0.shape) B_u_seq = jnp.broadcast_to(b_u, (steps,) + u0.shape) A_v_seq = jnp.broadcast_to(a_v, (steps,) + v0.shape) B_v_seq = jnp.broadcast_to(b_v, (steps,) + v0.shape) cumA_u, cumB_u = jax.lax.associative_scan(_linear_recurrence_combine, (A_u_seq, B_u_seq), axis=0) cumA_v, cumB_v = jax.lax.associative_scan(_linear_recurrence_combine, (A_v_seq, B_v_seq), axis=0) u_final = jnp.clip(cumA_u[-1] * u0 + cumB_u[-1], -SAFE_BOUND, SAFE_BOUND) v_final = jnp.clip(cumA_v[-1] * v0 + cumB_v[-1], -SAFE_BOUND, SAFE_BOUND) return jnp.concatenate([u_final, v_final], axis=-1).astype(COMPUTE_DTYPE) class CausalSelfAttention(nn.Module): num_heads: int d_model: int @nn.compact def __call__(self, x): B, L, D = x.shape d_head = self.d_model // self.num_heads Q = nn.Dense(self.d_model, dtype=COMPUTE_DTYPE, name="q")(x) K = nn.Dense(self.d_model, dtype=COMPUTE_DTYPE, name="k")(x) V = nn.Dense(self.d_model, dtype=COMPUTE_DTYPE, name="v")(x) Q = Q.reshape(B, L, self.num_heads, d_head).transpose(0, 2, 1, 3) K = K.reshape(B, L, self.num_heads, d_head).transpose(0, 2, 1, 3) V = V.reshape(B, L, self.num_heads, d_head).transpose(0, 2, 1, 3) inv_freq = rope_frequencies(d_head) positions = jnp.arange(L) Q = apply_rope(Q, positions, inv_freq) K = apply_rope(K, positions, inv_freq) try: Qb = Q.transpose(0, 2, 1, 3) Kb = K.transpose(0, 2, 1, 3) Vb = V.transpose(0, 2, 1, 3) out = jax.nn.dot_product_attention(Qb, Kb, Vb, is_causal=True) out = out.reshape(B, L, self.d_model) except (AttributeError, TypeError, NotImplementedError): import math as _m scores = jnp.matmul(Q, K.transpose(0, 1, 3, 2)) / _m.sqrt(d_head) idx = jnp.arange(L) causal_mask = jnp.where(idx[:, None] >= idx[None, :], 0.0, -1e9) scores = scores + causal_mask[None, None, :, :] attn = nn.softmax(scores, axis=-1) out = jnp.matmul(attn, V) out = out.transpose(0, 2, 1, 3).reshape(B, L, self.d_model) return nn.Dense(self.d_model, dtype=COMPUTE_DTYPE, name="out_proj")(out) class SwiGLUFFN(nn.Module): d_model: int hidden_dim: int @nn.compact def __call__(self, x): swiglu_hidden = max(1, int(self.hidden_dim * 2 / 3)) gate = nn.Dense(swiglu_hidden, dtype=COMPUTE_DTYPE, name="gate")(x) up = nn.Dense(swiglu_hidden, dtype=COMPUTE_DTYPE, name="up")(x) activated = nn.swish(gate) * up return nn.Dense(self.d_model, dtype=COMPUTE_DTYPE, name="down")(activated) class HTDNBlock(nn.Module): d_model: int d_u: int d_v: int hidden_react: int n_attractors: int num_heads: int @nn.compact def __call__(self, x, steps): x = x + DynamicsLayer(self.d_u, self.d_v, self.n_attractors, self.num_heads)( nn.RMSNorm(dtype=COMPUTE_DTYPE)(x), steps ) x = x + CausalSelfAttention(self.num_heads, self.d_model)(nn.RMSNorm(dtype=COMPUTE_DTYPE)(x)) x = x + SwiGLUFFN(self.d_model, self.hidden_react)(nn.RMSNorm(dtype=COMPUTE_DTYPE)(x)) return x class HTDNModel(nn.Module): vocab_size: int d_model: int d_u: int d_v: int hidden_react: int n_attractors: int num_heads: int num_layers: int max_len: int @nn.compact def __call__(self, input_ids, steps): embed = nn.Embed(self.vocab_size, self.d_model, dtype=COMPUTE_DTYPE, name="embed") x = embed(input_ids) for _ in range(self.num_layers): x = HTDNBlock(self.d_model, self.d_u, self.d_v, self.hidden_react, self.n_attractors, self.num_heads)(x, steps) x = nn.RMSNorm(dtype=COMPUTE_DTYPE)(x) embed_table = embed.embedding.astype(jnp.float32) logits = jnp.matmul(x.astype(jnp.float32), embed_table.T) return logits DEFAULT_CFG = dict( D_MODEL=1024, D_U=512, D_V=512, HIDDEN_REACT=3072, N_ATTRACTORS=22_000, NUM_HEADS=8, NUM_LAYERS=4, MAX_LEN=64, VOCAB_SIZE=119547, TOKENIZER="bert-base-multilingual-cased", ) def build_model_template(cfg: dict): model = HTDNModel( vocab_size=cfg["VOCAB_SIZE"], d_model=cfg["D_MODEL"], d_u=cfg["D_U"], d_v=cfg["D_V"], hidden_react=cfg["HIDDEN_REACT"], n_attractors=cfg["N_ATTRACTORS"], num_heads=cfg["NUM_HEADS"], num_layers=cfg["NUM_LAYERS"], max_len=cfg["MAX_LEN"], ) dummy = jnp.zeros((1, cfg["MAX_LEN"]), dtype=jnp.int32) key = jax.random.PRNGKey(0) params = model.init(key, dummy, steps=10)["params"] return model, params # ════════════════════════════════════════════════════════════════════════════════ # 3.5 🆕 تحميل + تشغيل تلقائي بالكامل من Hugging Face # هذا القسم يُنسخ (مع كل ما فوقه) إلى المستودع كـ modeling_htdn.py، # فيصبح استخدام النموذج لاحقاً مجرد استدعاء دالة أو دالتين، بلا أي # خطوات يدوية (لا تنزيل يدوي، لا json.load يدوي، لا flatten/unflatten # يدوي، لا إعادة بناء نموذج يدوية). # ════════════════════════════════════════════════════════════════════════════════ DEFAULT_DYNAMICS_STEPS = 10 # نفس القيمة المستخدَمة في build_model_template أعلاه def load_htdn_from_hub(repo_id: str, revision: str = None): """ يُحمّل نموذج HTDN كاملاً وتلقائياً من Hugging Face Hub: ينزّل config.json و model.safetensors والمحلل اللغوي، يعيد بناء بنية النموذج بنفس القياسات المحفوظة، ويستعيد الأوزان فيها — كل هذا باستدعاء واحد بدل الخطوات اليدوية القديمة. الاستخدام: model, params, tokenizer, config = load_htdn_from_hub("gijl/htdn-arabic-350m1") """ config_path = hf_hub_download(repo_id, "config.json", revision=revision) weights_path = hf_hub_download(repo_id, "model.safetensors", revision=revision) with open(config_path, "r", encoding="utf-8") as f: raw_cfg = json.load(f) cfg = { "D_MODEL": raw_cfg["d_model"], "D_U": raw_cfg["d_u"], "D_V": raw_cfg["d_v"], "HIDDEN_REACT": raw_cfg["hidden_react"], "N_ATTRACTORS": raw_cfg["n_attractors"], "NUM_HEADS": raw_cfg["num_heads"], "NUM_LAYERS": raw_cfg["num_layers"], "MAX_LEN": raw_cfg["max_len"], "VOCAB_SIZE": raw_cfg["vocab_size"], "TOKENIZER": raw_cfg.get("tokenizer", DEFAULT_CFG["TOKENIZER"]), } model, _ = build_model_template(cfg) # نبني قالباً فقط لتحديد البنية والأشكال flat = {} with safe_open(weights_path, framework="numpy") as f: for k in f.keys(): flat[k] = jnp.array(f.get_tensor(k)) params = unflatten_dict(flat, sep=".") tokenizer = AutoTokenizer.from_pretrained(repo_id, revision=revision) log(f"✅ تم تحميل {repo_id} تلقائياً (خطوة الحفظ المسجَّلة: {raw_cfg.get('checkpoint_step', '؟')})") return model, params, tokenizer, cfg def generate(model, params, tokenizer, prompt: str, max_new_tokens: int = 40, steps: int = DEFAULT_DYNAMICS_STEPS, temperature: float = 0.8, top_k: int = 40, seed: int = 0) -> str: """ توليد نص تلقائي بالكامل ابتداءً من prompt (بحث top-k بسيط، أو argmax لو top_k=0). ينتبه: هذه بنية مخصّصة لا تدعم KV-cache كمعمارية transformers القياسية، لذا كل خطوة تُعيد حساب نافذة السياق كاملة — مناسب للتجربة والتحقق، وقد يحتاج تحسيناً للاستخدام بكميات كبيرة. """ max_len = model.max_len ids = tokenizer(prompt, return_tensors="np")["input_ids"][0].tolist() rng = jax.random.PRNGKey(seed) pad_id = tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0 for _ in range(max_new_tokens): window = ids[-max_len:] pad = max_len - len(window) input_ids = jnp.array([[pad_id] * pad + window], dtype=jnp.int32) logits = model.apply({"params": params}, input_ids, steps=steps) next_logits = logits[0, -1].astype(jnp.float32) / max(temperature, 1e-5) if top_k and top_k > 0: k = min(top_k, next_logits.shape[-1]) top_vals, top_idx = jax.lax.top_k(next_logits, k) rng, sub = jax.random.split(rng) choice = jax.random.categorical(sub, top_vals) next_id = int(top_idx[choice]) else: next_id = int(jnp.argmax(next_logits)) ids.append(next_id) if tokenizer.eos_token_id is not None and next_id == tokenizer.eos_token_id: break return tokenizer.decode(ids, skip_special_tokens=True) def quick_generate(repo_id: str, prompt: str = "مرحبا، كيف حالك؟", max_new_tokens: int = 40, **gen_kwargs) -> str: """ الاختصار الكامل: من اسم المستودع إلى نص مُولَّد بسطر واحد فعلياً. from modeling_htdn import quick_generate print(quick_generate("gijl/htdn-arabic-350m1", "اكتب لي قصة قصيرة")) """ model, params, tokenizer, _ = load_htdn_from_hub(repo_id) return generate(model, params, tokenizer, prompt, max_new_tokens=max_new_tokens, **gen_kwargs) # ════════════════════════════════════════════════════════════════════════════════ # 4. استعادة نقطة التفتيش (من Orbax، أثناء التصدير فقط) # ════════════════════════════════════════════════════════════════════════════════ def restore_params(ckpt_root: str, step: int, params_template): writable_root = get_writable_checkpoint_root(ckpt_root, step) mgr = ocp.CheckpointManager( writable_root, options=ocp.CheckpointManagerOptions(max_to_keep=None, save_interval_steps=1), ) try: log(f"♻️ استعادة نقطة التفتيش عند الخطوة {step:,} من {writable_root} ...") restored = mgr.restore(step, args=ocp.args.StandardRestore(params_template)) finally: mgr.close() log("✅ اكتملت الاستعادة") return restored # ════════════════════════════════════════════════════════════════════════════════ # 5. تحويل المعاملات إلى safetensors # ════════════════════════════════════════════════════════════════════════════════ def params_to_safetensors(params, out_path: str): flat = flatten_dict(params, sep=".") tensors = {} total_params = 0 for k, v in flat.items(): arr = np.asarray(jax.device_get(v)) tensors[k] = arr total_params += arr.size save_file(tensors, out_path) size_mb = os.path.getsize(out_path) / 1e6 log(f"💾 حُفظت المعاملات في {out_path} ({size_mb:.1f}MB، {total_params/1e6:.1f}M معامل، " f"{len(tensors)} مصفوفة)") return total_params # ════════════════════════════════════════════════════════════════════════════════ # 6. تجميع ملفات المستودع (config, tokenizer, README, كود النموذج) # ════════════════════════════════════════════════════════════════════════════════ MODEL_CARD_TEMPLATE = """--- language: ar license: apache-2.0 tags: - htdn - jax - flax - text-generation - arabic --- # HTDN — نموذج لغوي مخصّص (JAX/Flax) نموذج HTDN (بنية مخصّصة تجمع بين طبقة ديناميكيات متكرّرة وانتباه ذاتي سببي مع RoPE و SwiGLU) دُرِّب على بيانات عربية متعددة المصادر (streaming) على GPU T4×2 عبر Kaggle. ⚠️ هذه **ليست** معمارية `transformers` قياسية، لذا لا يمكن تحميلها مباشرة عبر `AutoModel.from_pretrained`. لكن التحميل والتشغيل صارا تلقائيَّين بالكامل عبر دالتين جاهزتين مرفوعتين ضمن `modeling_htdn.py` في هذا المستودع — لا حاجة لأي تفكيك/تجميع يدوي للأوزان. ## طريقة الاستخدام (تلقائية بالكامل) ```python pip install -q jax flax transformers huggingface_hub safetensors from huggingface_hub import hf_hub_download import importlib.util # خطوة تنزيل الكود المخصّص — لا مفرّ منها لأن هذه بنية غير قياسية path = hf_hub_download("{repo_id}", "modeling_htdn.py") spec = importlib.util.spec_from_file_location("modeling_htdn", path) modeling_htdn = importlib.util.module_from_spec(spec) spec.loader.exec_module(modeling_htdn) # تحميل تلقائي كامل: النموذج + الأوزان + المحلل اللغوي model, params, tokenizer, config = modeling_htdn.load_htdn_from_hub("{repo_id}") # توليد نص تلقائي كامل print(modeling_htdn.generate(model, params, tokenizer, "مرحبا")) # أو الاختصار الكامل بسطر واحد فعلي: # print(modeling_htdn.quick_generate("{repo_id}", "مرحبا")) ``` ## معلومات التدريب - الخطوة عند الرفع: {step} - عدد المعاملات: ~{n_params_m:.1f}M - المحلل اللغوي: `{tokenizer_name}` - أفضل خسارة مسجَّلة: {best_loss} - الزمن التراكمي للتدريب: {elapsed} هذا نموذج تجريبي/بحثي — النتائج أولية. """ def build_config_json(cfg: dict, step: int, meta: dict) -> dict: return { "model_type": "htdn", "architectures": ["HTDNModel"], "vocab_size": cfg["VOCAB_SIZE"], "d_model": cfg["D_MODEL"], "d_u": cfg["D_U"], "d_v": cfg["D_V"], "hidden_react": cfg["HIDDEN_REACT"], "n_attractors": cfg["N_ATTRACTORS"], "num_heads": cfg["NUM_HEADS"], "num_layers": cfg["NUM_LAYERS"], "max_len": cfg["MAX_LEN"], "tokenizer": cfg["TOKENIZER"], "checkpoint_step": step, "framework": "jax/flax", "training_meta": { "best_loss": meta.get("best_loss"), "total_elapsed_readable": meta.get("total_elapsed_readable"), }, } def assemble_export_dir(export_dir: str, cfg: dict, step: int, meta: dict, this_script_path: str): os.makedirs(export_dir, exist_ok=True) # config.json config = build_config_json(cfg, step, meta) with open(os.path.join(export_dir, "config.json"), "w", encoding="utf-8") as f: json.dump(config, f, ensure_ascii=False, indent=2) # tokenizer log(f"🔤 حفظ المحلل اللغوي ({cfg['TOKENIZER']}) ...") tok = AutoTokenizer.from_pretrained(cfg["TOKENIZER"]) tok.save_pretrained(export_dir) # كود بناء النموذج — نسخة من هذا الملف نفسه فيه كل تعريفات الطبقات # ودالتَي load_htdn_from_hub/generate، فيصبح الاستخدام لاحقاً تلقائياً shutil.copy(this_script_path, os.path.join(export_dir, "modeling_htdn.py")) # README.md (بطاقة النموذج) n_params_m = 0.0 # يُملأ لاحقاً بعد التحويل الفعلي (انظر main) readme = MODEL_CARD_TEMPLATE.format( repo_id=HF_REPO_ID, step=step, n_params_m=n_params_m, tokenizer_name=cfg["TOKENIZER"], best_loss=meta.get("best_loss", "غير معروف"), elapsed=meta.get("total_elapsed_readable", "غير معروف"), ) with open(os.path.join(export_dir, "README.md"), "w", encoding="utf-8") as f: f.write(readme) return export_dir def update_readme_param_count(export_dir: str, n_params: int): path = os.path.join(export_dir, "README.md") with open(path, "r", encoding="utf-8") as f: content = f.read() content = content.replace("~0.0M معامل", f"~{n_params/1e6:.1f}M معامل") with open(path, "w", encoding="utf-8") as f: f.write(content) # ════════════════════════════════════════════════════════════════════════════════ # 7. الرفع إلى Hugging Face # ════════════════════════════════════════════════════════════════════════════════ def push_to_hub(export_dir: str, repo_id: str, private: bool, commit_message: str): log(f"☁️ إنشاء/التحقق من المستودع {repo_id} (خاص={private}) ...") create_repo(repo_id, private=private, exist_ok=True, repo_type="model") log(f"⬆️ رفع الملفات من {export_dir} ...") upload_folder( repo_id=repo_id, folder_path=export_dir, commit_message=commit_message, repo_type="model", ) log(f"✅ تم الرفع بنجاح → https://huggingface.co/{repo_id}") # ════════════════════════════════════════════════════════════════════════════════ # 8. 🆕 اختبار تلقائي بعد الرفع: تنزيل ما رُفع للتو وتوليد عيّنة نصية # ════════════════════════════════════════════════════════════════════════════════ def self_test_after_upload(repo_id: str, prompt: str, max_new_tokens: int): log("🧪 اختبار تلقائي: تنزيل النموذج المرفوع للتو وتوليد عيّنة نصية " "للتأكد أن كل شيء (بما فيه الاستخدام لاحقاً) يعمل تلقائياً ...") sample = quick_generate(repo_id, prompt=prompt, max_new_tokens=max_new_tokens) log(f"📝 المُدخَل: {prompt}") log(f"📝 النص المولَّد تلقائياً: {sample}") return sample # ════════════════════════════════════════════════════════════════════════════════ # main # ════════════════════════════════════════════════════════════════════════════════ def main(): if "your-username" in HF_REPO_ID: raise RuntimeError("عدّل HF_REPO_ID أعلى الملف قبل التشغيل (اسم المستخدم/اسم المستودع).") login_hf() ckpt_root, step, meta_path = find_checkpoint_source() meta = {} cfg = dict(DEFAULT_CFG) if meta_path and os.path.exists(meta_path): with open(meta_path, "r", encoding="utf-8") as f: meta = json.load(f) saved_cfg = meta.get("config", {}) # تطابق أسماء الحقول المحفوظة في training_meta.json مع DEFAULT_CFG cfg.update({ "D_MODEL": saved_cfg.get("D_MODEL", cfg["D_MODEL"]), "D_U": saved_cfg.get("D_U", cfg["D_U"]), "D_V": saved_cfg.get("D_V", cfg["D_V"]), "HIDDEN_REACT": saved_cfg.get("HIDDEN_REACT", cfg["HIDDEN_REACT"]), "N_ATTRACTORS": saved_cfg.get("N_ATTRACTORS", cfg["N_ATTRACTORS"]), "NUM_HEADS": saved_cfg.get("NUM_HEADS", cfg["NUM_HEADS"]), "NUM_LAYERS": saved_cfg.get("NUM_LAYERS", cfg["NUM_LAYERS"]), "MAX_LEN": saved_cfg.get("MAX_LEN", cfg["MAX_LEN"]), "VOCAB_SIZE": saved_cfg.get("VOCAB_SIZE", cfg["VOCAB_SIZE"]), "TOKENIZER": saved_cfg.get("TOKENIZER", cfg["TOKENIZER"]), }) if meta.get("step") != step: log(f" ⚠️ خطوة الميتاداتا ({meta.get('step')}) لا تطابق آخر checkpoint ({step:,}) " f"— سيُستخدَم فقط الـ checkpoint، والإحصائيات الأخرى قد تكون غير دقيقة") log(f"⚙️ الإعدادات المستخدَمة: {json.dumps(cfg, ensure_ascii=False)}") log("🧠 إعادة بناء قالب النموذج (لأجل الاستعادة فقط) ...") model, params_template = build_model_template(cfg) params = restore_params(ckpt_root, step, params_template) export_dir = assemble_export_dir(LOCAL_EXPORT_DIR, cfg, step, meta, os.path.abspath(__file__)) weights_path = os.path.join(export_dir, "model.safetensors") n_params = params_to_safetensors(params, weights_path) update_readme_param_count(export_dir, n_params) push_to_hub(export_dir, HF_REPO_ID, PRIVATE_REPO, COMMIT_MSG) if RUN_SELF_TEST_AFTER_UPLOAD: try: self_test_after_upload(HF_REPO_ID, DEMO_PROMPT, DEMO_MAX_NEW_TOKENS) except Exception as e: log(f"⚠️ تعذّر الاختبار التلقائي بعد الرفع (هذا لا يؤثر على نجاح الرفع نفسه): {e}") if __name__ == "__main__": main()