Transformers documentation

تحميل الأوزان الديناميكي

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.1).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

تحميل الأوزان الديناميكي

غالبًا ما تُسلسَل نقاط التفتيش (Checkpoints) بتنسيق لا يتطابق مع ما يتوقعه النموذج وقت التشغيل. وتشمل السيناريوهات الشائعة ما يلي:

  1. الأوزان المدمجة: تحفظ نقاط التفتيش أوزانًا منفصلة لـgate_proj وup_proj، لكن النموذج يستخدم gate_up_proj مدمجًا من أجل الكفاءة.
  2. توحيد خبراء MoE: يجب تجميع أوزان الخبراء الفردية (experts.0.weight، experts.1.weight، …) في متجه ثلاثي الأبعاد واحد.
  3. التسمية القديمة: تستخدم نقاط التفتيش القديمة تسميات مختلفة (مثل LayerNorm.gamma مقابل LayerNorm.weight).
  4. النماذج المركبة: يحتوي نموذج اللغة والرؤية على وحدتي فرعية من نوع PreTrainedModel، ولكل منهما اتفاقية نقاط تفتيش خاصة به.
  5. التكميم: قد تُخزَّن الأوزان في صيغ مكمّمة تتطلب فكّ التسلسل.

يعالج تحميل الأوزان الديناميكي هذه المشكلة من خلال تطبيق عمليات مجدولة وقابلة للعكس على متجهات نقطة الحفظ أثناء تحميلها. يوفّر WeightConverter وWeightRenaming هذه الميزة، حيث تصف كيفية خرائط مفتاح واحد أو أكثر من نقاط الحفظ إلى معامل واحد أو أكثر من النموذج، وأي عمليات التحويل القابلة للتركيب ConversionOps التي يجب تنفيذها على المتجهات المطابقة. يتكيّف هذا النهج مع تخطيطات الأوزان الجديدة، ويدعم مزيج الخبراء (MoE) المكمّم، ويتكامل مع توازي المتجهات.

يُبيّن هذا الدليل كيفية استخدام WeightConverter لتحويل المتجهات. توجد خرائط التحويل في conversion_mapping.py؛ ويُفهرس كل تحويل مسجّل إما بواسطة سلسلة model_type (مثل "mixtral") أو بواسطة اسم فئة (مثل "LlavaModel").

خط أنابيب التحميل الكامل

تخضع جميع النماذج لنظام تحميل الأوزان الديناميكي. يُعدّ تعيين التحويل خطوة اختيارية ضمن ذلك النظام لا تُفعَّل إلا عندما يحتوي النموذج على إدخالات مسجّلة باسم فئته أو model_type.

Checkpoint File → from_pretrained() → convert_and_load_state_dict_in_model()
                                              ↓
                         ┌───────────────────────────────────────────────────────────┐
                         │  For each weight in checkpoint:                           │
                         │  1. Match renamed/processed source key to model parameter │
                         │  2. Shard the weight and send to device (async)           │
                         │  3. Collect tensors with the same source_pattern together │
                         │     (e.g. MoE experts, gate_up_proj)                      │
                         │  4. Apply dequantization/deserialization (if pre-quant)   │
                         │  5. Apply conversion (if defined)                         │
                         │  6. Apply quantization (if enabled and step 4 not used)   │
                         │  7. Set parameter on model                                │
                         └───────────────────────────────────────────────────────────┘
الخطوةعند تفعيله
التحميل الديناميكيدائمًا، لجميع النماذج
خريطة التحويلفقط عندما تكون فئة النموذج أو model_type مسجّلة في _MODEL_TO_CONVERSION_PATTERN
تقسيم TPفقط عند استخدام DistributedConfig(tp_size=N) وامتلاك النموذج base_model_tp_plan
إلغاء التكميم/إلغاء التسلسلفقط عند تحميل نقطة تفتيش مُكمّاة مسبقًا
التكميمفقط عند توفير إعدادات التكميم وعدم كون الأوزان مُكمَّمة مسبقًا

النماذج الكثيفة (مثل Llama)

بالنسبة لمعظم النماذج الكثيفة، يتطابق تنسيق نقطة الحفظ مع تنسيق النموذج مباشرة، لذا لا حاجة إلى خرائط تحويل. قد لا تزال بعض النماذج تتطلب إعادة تسمية (مثل تسميات الأسماء القديمة). لا يزال تقسيم TP ساري المفعول عند تفعيله.

Checkpoint:                             Model:
model.layers.0.self_attn.q_proj.weight  →  model.layers.0.self_attn.q_proj.weight
model.layers.0.self_attn.k_proj.weight  →  model.layers.0.self_attn.k_proj.weight
model.layers.0.mlp.gate_proj.weight     →  model.layers.0.mlp.gate_proj.weight
model.layers.0.mlp.up_proj.weight       →  model.layers.0.mlp.up_proj.weight
model.layers.0.mlp.down_proj.weight     →  model.layers.0.mlp.down_proj.weight

قد تستخدم نقاط التفتيش القديمة تسميات أقدم يتم التعامل معها عبر إعادة تسمية مدمجة تُطبَّق على جميع النماذج:

Checkpoint:                          Model:
LayerNorm.gamma              →       LayerNorm.weight
LayerNorm.beta               →       LayerNorm.bias

نماذج MoE (مثل Mixtral)

في نماذج MoE، يختلف تنسيق نقطة التفتيش عن تنسيق النموذج. يقوم خرائط التحويل بتحويل أوزان الخبراء المنفصلة إلى متجهات ثلاثية الأبعاد مدمجة، وتُطبَّق تقسيمات TP بعد التحويل.

Checkpoint:                              Model:
experts.0.w1.weight  ─┐
experts.1.w1.weight   │ MergeModulelist
...                   ├───────────────→  experts.gate_up_proj (8, hidden, 2*intermediate)
experts.0.w3.weight   │ + Concatenate
experts.1.w3.weight  ─┘

النماذج المركبة (مثل نماذج الرؤية واللغة)

قد يحتوي PreTrainedModel على وحدات فرعية أخرى من نوع PreTrainedModel. يمكن لكل نموذج فرعي أن يملك خريطته الخاصة للتحويل، المسجلة إما باسم صفته أو بـmodel_type الخاص به. عند تحميل النموذج الأب، يتجول get_model_conversion_mapping في النماذج الفرعية بترتيب عمق أولاً، ويجمع خرائطها، ويحدد نطاق كل تحويل تلقائياً إلى مسار وحدته الفرعية عبر scope_prefix.

Composite model:                              Per-submodel mappings (auto-scoped):
LlavaForConditionalGeneration
├── vision_model: SiglipVisionModel    →     SiglipVisionModel mapping (scope="vision_model")
└── language_model: LlamaForCausalLM   →     LlamaForCausalLM mapping  (scope="language_model")

scope_prefix هو المسار المنقّط للوحدة الفرعية ("vision_model"، "language_model.model"، وما إلى ذلك). لا يُطلق التحويل المُحدَّد النطاق إلا على المفاتيح التي تبدأ بـ f"{scope_prefix}."؛ حيث يُستأصل البادئة قبل مطابقة النمط ثم تُعاد إلحاقها بعد الاستبدال، بحيث تُكتب خرائط كل نموذج فرعي بشكل نسبي بالنسبة للنموذج الفرعي، تمامًا كما لو كان هو الجذر.

البنية

يُبنى النظام حول عدة مكونات رئيسية معرّفة في core_model_loading.py:

المرحلة 1 — المعالجة لكل مفتاح (يتكرر على مفاتيح نقطة التفتيش):

  1. مرّر قائمة التحويلات مرة واحدة. كل WeightRenaming يطابق يُفعَّل (مثلًا block_sparse_moe → mlp)، وقد يدّعي مفتاح واحد على الأكثر WeightConverter (مثلًا experts.*.w1.weight).
  2. تجزئة (TP) والإرسال إلى الجهاز بشكل غير متزامن عبر ThreadPoolExecutor.
  3. اجمع الموترات التي تشترك في نفس source_pattern معاً (على سبيل المثال، جميع أوزان خبراء MoE، أو إسقاطات gate + up).

المرحلة 2 — المعالجة لكل تعيين (تتكرر على التعيينات المجموعة):

  1. إلغاء التكميم/إلغاء التسلسل (نقاط التفتيش المُكمَّمة مسبقًا فقط).
  2. تطبيق سلسلة ConversionOps: Chunk، Concatenate، MergeModulelist، Transpose، وما إلى ذلك.
  3. تكميم أثناء التشغيل (إذا لم يكن مُكمَّمًا مسبقًا).
  4. تعيين المعامل على النموذج.

WeightTransform

الفئة الأساسية التي تتعامل مع مطابقة الأنماط وجمع المصفوفات:

  • تجميع الأنماط: أنماط المصدر هي تعبيرات نمطية كاملة تُطابَق باستخدام re.search(). يطابق الرمز البرّي * أي مكوّن قابل للفهرسة ويجمع جميع التطابقات معاً لعمليات الدفعة.
  • مجموعات الالتقاط والمراجع الرجعية: يمكن الإشارة إلى مجموعات الالتقاط في الأنماط المصدرية على أنها \1، \2، … في الأنماط المستهدفة للحفاظ على المقاطع الفرعية (مثل فهارس الطبقات) عبر عملية إعادة التسمية.
  • تحديد النطاق: scope_prefix (يُعيَّن تلقائيًا لكل نموذج فرعي بواسطة get_model_conversion_mapping) يقيّد التحويل بالمفاتيح الموجودة تحت ذلك المسار. يُستأصل البادئة قبل المطابقة ثم تُعاد إلحاقها بعد الاستبدال.
  • إعادة تسمية المفاتيح: يطبّق rename_source_key() تعبيرًا منتظمًا (مع معالجة النطاق) ويعيد (renamed_key, source_pattern) حتى يعرف المُحمِّل أي مُحوِّل، إن وُجد، ادّعى المفتاح.
  • مجموعة المصفوفات: تُراكم الدالة add_tensor() المصفوفات المُحلّة (أو Future) تحت source_pattern الخاص بها، بحيث تُجمَّع جميع المصفوفات المطلوبة لتحويل واحد (مثل أوزان جميع خبراء MoE) معًا قبل تشغيل سلسلة العمليات.
  • قابلية العكس: reverse_transform() يبدّل أنماط المصدر ↔ الهدف ويعكس كل عملية، بحيث تُستخدم نفس القائمة المعكوسة لتوجيه عملية الحفظ.
  • التتبع: يُبلّغ was_used() عما إذا كان التحويل قد طابق أي مفتاح فعليًا أثناء التحميل؛ وهذا ضروري حتى يمكن إعادة تطبيق إعادة التسميات غير الثنائية (مثل PrefixChange الذي يضيف بادئة قد تكون موجودة مسبقًا) بشكل متماثل عند الحفظ.

WeightRenaming

WeightRenaming هو WeightTransform مخصص لإعادة تسمية المفاتيح فقط دون أي عمليات على المصفوفات. على عكس WeightConverter، لا يقوم WeightRenaming بـ claim للمفتاح، مما يسمح بسلسلة إعادة تسمية متعددة بحرية. في مسار التحميل، تُنفَّذ جميع عمليات إعادة التسمية قبل المحوّل؛ وفي مسار الحفظ (القائمة المعكوسة)، تُنفَّذ جميع عمليات إعادة التسمية المعكوسة بعد المحوّل المعكوس — راجع قاعدة الترتيب أعلاه.

# Legacy checkpoint compatibility
WeightRenaming("LayerNorm.gamma", "LayerNorm.weight")

# Module path changes
WeightRenaming(".block_sparse_moe.", ".mlp.")

PrefixChange هو غلاف على مستوى أعلى لـ WeightRenaming للحالة الشائعة المتمثلة في إزالة أو إضافة مكوّن مسار كامل. يحدد الخياري model_prefix نطاق العملية على المفاتيح الموجودة تحت ذلك الاسم:

# "model.layers.bad_prefix.weight" → "model.layers.weight"
PrefixChange(prefix_to_remove="bad_prefix", model_prefix="model.layers")
# "layers.0.weight" → "model.layers.0.weight"
PrefixChange(prefix_to_add="model")

WeightConverter

يمتد WeightConverter من WeightTransform مع سلسلة من ConversionOps تعمل على الموترات المجمّعة. الأربعة أنواع المدعومة من حيث العدد هي:

العددأنماط المصدرأنماط الاستهدافالعملية النموذجية
واحد لواحد11Transpose، PermuteForRope
من واحد إلى متعدد1>1تجزئة (مثل فك حزمة qkv_proj)
من متعدد إلى واحد>11Concatenate، MergeModulelist (مثل دمج الخبراء)
من-إلى-كثير>1>1فقط مع العمليات التي تدعمه بشكل صريح (مثل ErnieFuseAndSplitTextVisionExperts)
WeightConverter(
    source_patterns=[".experts.*.w1.weight", ".experts.*.w3.weight"],
    target_patterns=".experts.gate_up_proj",
    operations=[MergeModulelist(dim=0), Concatenate(dim=1)],
)

يُعدّ WeightConverter قابلًا للعكس أيضًا: حيث يقوم reverse_transform() بتبديل المصدر ↔ الهدف، ويستبدل كل عملية بـ reverse_op الخاص بها، مما يجعل خرائط التحويل المسجّلة ثنائية الاتجاه بالبنية (فالتحميل يستخدم القائمة كما هي، بينما يستخدم الحفظ القائمة معكوسة).

ترتيب عمليات إعادة التسمية والمحوّلات

قائمة إدخالات WeightRenaming قبل إدخالات WeightConverter، مع إبقاء أوراقها منفصلة: فعمليات إعادة التسمية تطبّع المفاتيح التي تستهلكها محوّلات الوزن، لكنها لا تستهدف أبداً ورقة ينتجها محوّل. يعتمد مسار الحفظ على ذلك لعكس الترميز في مرحلتين (عكس المحوّلات أولاً، ثم عكس عمليات إعادة التسمية).

يتم المرور عبر قائمة التحويلات بالترتيب، مرة واحدة لكل مفتاح نقطة تفتيش. لكل مفتاح:

  • كل WeightRenaming يطابق يُفعَّل؛ ويمكن أن تتسلسل عمليات إعادة التسمية المتعددة.
  • أول WeightConverter يطابق claims المفتاح. يتم تجاوز المحوِّلات اللاحقة — وهذا يضمن توجيه المصفوفة إلى محوِّل واحد فقط لخطوة الدمج/التقسيم، وهو السبب في أن وجود محوِّلَين بنطاقات متداخلة يُعدّ خطأً في الإعداد.
weight_mapping = [
    WeightRenaming("^old_prefix", "encoder"),              # rename runs always
    WeightConverter(                                       # converter claims the key
        "attn.qkv_proj.weight",
        ["attn.q_proj.weight", "attn.k_proj.weight", "attn.v_proj.weight"],
        operations=[Chunk(dim=0)],
    ),
]
# Load:  "old_prefix.attn.qkv_proj.weight"
#   → WeightRenaming  → "encoder.attn.qkv_proj.weight"
#   → WeightConverter → "encoder.attn.{q,k,v}_proj.weight"
#
# Save: list reversed, each transform inverted:
#   → rev(WeightConverter) repacks QKV → "encoder.attn.qkv_proj.weight"
#   → rev(WeightRenaming)  fixes prefix → "old_prefix.attn.qkv_proj.weight"

عمليات التحويل

تحتوي فئة WeightConverter على عدة عمليات تُنفَّذ عند استدعاء from_pretrained() لتحويل تينسورات المصدر من نقطة الحفظ إلى تينسورات الهدف في النموذج.

العمليات قابلة للعكس بالكامل. يعكس الحفظ التحويلات ويعيد نقطة التفتيش الأصلية، مما يتيح لك العمل بسهولة عبر أطر عمل مختلفة.

العمليةعكس
Chunk(dim)Concatenate(dim)
Concatenate(dim)Chunk(dim)
MergeModulelist(dim)SplitModulelist(dim)
SplitModulelist(dim)MergeModulelist(dim)
Transpose(d0, d1)Transpose(d1, d0)
PermuteForRope()PermuteForRope()
Conv3dToLinear(...)LinearToConv3d(...)

مقطع

يقوم عمل Chunk بتقسيم الموتر إلى أجزاء متساوية على طول بُعد معين. على سبيل المثال، إذا كان النموذج يتوقع Q و K و V كموترات منفصلة بدلاً من موتر واحد.

WeightConverter(
    "self_attn.qkv_proj",
    ["self_attn.q_proj", "self_attn.k_proj", "self_attn.v_proj"],
    operations=[Chunk(dim=0)],
)

دمج

تقوم عملية Concatenate بدمج متجهات منفصلة في متجه واحد. على سبيل المثال، إذا كان النموذج يتوقع Q و K و V كمتجه واحد بدلاً من متجهات منفصلة.

WeightConverter(
    ["self_attn.q_proj", "self_attn.k_proj", "self_attn.v_proj"],
    "self_attn.qkv_proj",
    operations=[Concatenate(dim=0)],
)

MergeModulelist

MergeModulelist يدمج قائمة من المصفوفات ثنائية الأبعاد في مصفوفة ثلاثية الأبعاد واحدة. على سبيل المثال، يمكنك تركيب MergeModulelist مع Concatenate لرتب الخبراء في نموذج MoE وتجميعهم في مصفوفة واحدة.

WeightConverter(
    ["block_sparse_moe.experts.*.w1.weight", "block_sparse_moe.experts.*.w3.weight"],
    "mlp.experts.gate_up_proj",
    operations=[MergeModulelist(dim=0), Concatenate(dim=1)],
)

SplitModulelist

يقوم SplitModulelist بتقسيم متجه ثلاثي الأبعاد (3D) مرة أخرى إلى قائمة من المتجهات ثنائية الأبعاد (2D). على سبيل المثال، يمكنك تقسيم مجموعة من الخبراء (experts) إلى خبراء فرديين.

WeightConverter(
    "mlp.experts.down_proj",
    "block_sparse_moe.experts.*.w2.weight",
    operations=[SplitModulelist(dim=0)],
)

PermuteForRope

يحوّل PermuteForRope الأوزان من التنسيق المتشابك إلى استخدام تنسيق sin/cos. على سبيل المثال، يمكنك تركيب Chunk مع PermuteForRope لتقسيم متجه QKV المدمج وتطبيق تبديل RoPE الخاص بـ sin/cos على Q و K.

WeightConverter(
    ["model.layers.*.self_attn.qkv_proj.weight"],
    [
        "model.layers.*.self_attn.q_proj.weight",
        "model.layers.*.self_attn.k_proj.weight",
        "model.layers.*.self_attn.v_proj.weight",
    ],
    operations=[Chunk(dim=0), PermuteForRope()],
)

استبدال

Transpose تبديل أبعاد الموتر. مفيد لتحويل تخطيطات الأوزان بين معايير مختلفة.

WeightConverter(
    source_patterns="mlp.gate.weight",
    target_patterns="mlp.text_moe.gate.weight",
    operations=[Transpose(dim0=0, dim1=1)],
)

سلسلة العمليات

يمكن ربط العمليات لتنفيذ تحويلات معقدة. تُنفَّذ العمليات بالترتيب، حيث يصبح ناتج كل عملية مدخلاً للعملية التالية.

مثال: تحويل Mixtral MoE

WeightConverter(
    source_patterns=[
        ".experts.*.w1.weight",  # gate_proj per expert
        ".experts.*.w3.weight",  # up_proj per expert
    ],
    target_patterns=".experts.gate_up_proj",
    operations=[
        MergeModulelist(dim=0),  # Stack all experts: (n_experts, in, out)
        Concatenate(dim=1),      # Fuse gate+up: (n_experts, in, 2*out)
    ],
)

تدفق البيانات:

Input:
  ".experts.*.w1.weight": [tensor_0, tensor_1, ..., tensor_7]  # 8 experts
  ".experts.*.w3.weight": [tensor_0, tensor_1, ..., tensor_7]  # 8 experts

After MergeModulelist(dim=0):
  ".experts.*.w1.weight": (8, 4096, 14336)  # stacked gate
  ".experts.*.w3.weight": (8, 4096, 14336)  # stacked up

After Concatenate(dim=1):
  ".experts.gate_up_proj": (8, 4096, 28672)  # fused gate_up

قواعد مطابقة الأنماط

  • أنماط المصدر هي تعبيرات نمطية كاملة تُقيَّم باستخدام re.search(). يثبّت ^ موضع البداية للمفتاح المحدَّد النطاق (بعد إزالة scope_prefix).
  • * هو نمط عام لكل فهرس يجمع جميع المتجهات المطابقة تحت نفس نمط المصدر (مع الحفاظ على ترتيب نقاط الحفظ لضمان دقة الدمج).
  • يمكن الإشارة إلى المجموعات الملتقطة في الأنماط المصدرية على أنها \1، \2، … في الأنماط المستهدفة للحفاظ على أجزاء من المفتاح الأصلي (مثل فهارس الطبقات).
  • تطابق التحويلات النطاقية (عند ضبط scope_prefix) فقط مع المفاتيح التي تبدأ بـ f"{scope_prefix}.".

تسجيل خريطة تحويل

تُسجَّل قائمة تحويل مقابل مفتاح نصي — إما model_type (مثل "mixtral") أو اسم فئة (مثل "LlavaModel"):

from transformers.conversion_mapping import register_checkpoint_conversion_mapping

register_checkpoint_conversion_mapping(
    "my_model_type",
    [
        WeightRenaming(".old.", ".new."),
        WeightConverter(
            ".experts.*.w1.weight",
            ".experts.gate_proj",
            operations=[MergeModulelist(dim=0)],
        ),
    ],
)

قواعد الاستعلام

عندما يعالج get_model_conversion_mapping نموذجًا من نوع PreTrainedModel، يتم زيارة كل نموذج فرعي من نوع PreTrainedModel بترتيب DFS (أي nn.Module.named_modules() مُصفّى ليحتوي على حالات PreTrainedModel فقط). يتم تحديد كل نموذج فرعي في مرحلتين: حيث يُنفَّذ مرشح الكود المخصص أولًا، ثم يُبحث عن التحويل المسجَّل.

تُتخطى نماذج الأكواد المخصصة قبل حدوث أي عملية بحث. يُحتسب النموذج الفرعي كنموذج مخصص إذا أعادت الدالة is_custom_code() القيمة True، وهذا يشمل النماذج المحمّلة من Hub باستخدام trust_remote_code=True وأي فئة نموذج غير موجودة في Transformers. لا ترث نماذج الأكواد المخصصة خرائط التحويل، لأن اسم الفئة أو model_type قد يتعارض مع نموذج أصلي. على سبيل المثال، البنية المخصصة التي تُعيّن model_type="mixtral" لن تحصل على تحويلات دمج الخبراء المدمجة في Mixtral.

سجّل نموذج كود مخصص صراحةً باستخدام register_checkpoint_conversion_mapping لتطبيق خريطة تحويل. يضيف التسجيل اسم الفئة أو model_type إلى مجموعة خرائط التسجيل التي قام بها المستخدم، مما يعفي النموذج من تخطي الكود المخصص ويوجّهه عبر البحث العادي أدناه.

لكل نموذج فرعي يجتاز مرشح الشيفرة المخصصة:

  1. يتم أولاً محاولة البحث باسم الفئة، ثم model_type. إذا كان كلاهما مسجلاً لنفس الوحدة، فإن تعيين اسم الفئة هو الغالب ويُتجاهل تعيين model_type لتلك الوحدة — وهذا يتيح لرأس المهمة (مثل LlavaForConditionalGeneration) تجاوز خط الأساس المشترك model_type ("llava").
  2. يتم تعيين scope_prefix في الترميز المحدد إلى المسار المنقّط للوحدة الفرعية ("" للجذر).
  3. Ancestor-based deduplication يقرر ما إذا كان سيحتفظ بالتعيين:
    • إذا كان مسار السلف قد سبق واحتجز المعرّف نفسه (اسم الفئة أو model_type)، يتم تخطي الوحدة الفرعية — لأن تعيين السلف غير النطاق أو ذو النطاق الأعلى يغطي هذا الشجرة الفرعية بالفعل.
    • إذا كان الأخ هو من ادّعى الحق فيه فقط، فإن الوحدة الفرعية تُحفظ مع scope_prefix الخاص بها. يحصل كل أخ على خريطته النطاقية الخاصة.

تُتتبَّع قوائم «المشاهَد» الخاصة باسم الفئة وmodel_type بشكل منفصل، مع وجود تفصيل دقيق: عند مطابقة وحدة عبر اسم فئتها، لا يُضاف model_type الخاص بها إلى قائمة المشاهَد بشكل صريح. وذلك حتى تبقى الوحدات الأخرى التي تشترك في نفس model_type لكن دون وجود إسناد محدد للفئة (مثل DetrModel تحت DetrForSegmentation) قابلة للوصول من خلال البحث في model_type.

الخرائط القائمة على الفئات مقابل مستعرات model_type

تتعايش الأسلوبان معًا في _MODEL_TO_CONVERSION_PATTERN:

_MODEL_TO_CONVERSION_PATTERN = {
    # model_type aliases (lookup by config.model_type)
    "minimax": "mixtral",
    "qwen3_moe": "qwen2_moe",
    "mistral3": "llava",
    # class-name aliases (lookup by type(submodule).__name__)
    "PaliGemmaModel": "LlavaModel",
    "MaskFormerDetrDecoder": "DetrModel",
    ...
}

يُفضَّل استخدام مفاتيح اسم الفئة عندما يكون model_type مشتركًا، لكن فئة محددة تتطلب سلوكًا مختلفًا.

تكامل تقسيم الموتر

يتكامل نظام التحميل الديناميكي مع توازي الموترات (TP) من خلال التسلسل الهرمي TensorParallelLayer المُعرَّف في src/transformers/integrations/tensor_parallel.py.

عند تفعيل TP، يتم تقسيم المتجهات أثناء الماديّة (materialization)، وليس بعد ذلك. وهذا يعني أن كل رتبة (rank) لا تُحمّل سوى الجزء من المتجه الذي تحتاجه.

def spawn_tp_materialize(thread_pool, tensor, sharding_method, tensor_idx, device, dtype):
    def _job():
        return sharding_method.shard_tensor(tensor, tensor_idx=tensor_idx, device=device, dtype=dtype)
    return thread_pool.submit(_job)

أنماط التوازي المتاحة

الأسلوببُعد شريحة الأوزانالوصف
colwise-2على مستوى الأعمدة: تقسيم ميزات الإخراج
rowwise-1على مستوى الصفوف: تم تقسيم ميزات الإدخال
packed_colwise-2لأوزان مُدمجة (gate_up_proj)
packed_rowwise-1لأوزان مدمجة
embedding_rowwise0توازي المفردات
grouped_gemm0التوازي الخبير لـ MoE
sequence_parallelلا شيءلا يوجد تقسيم للأوزان

معالجة الأوزان المعبأة

يتطلب الأوزان المدمجة مثل gate_up_proj عناية خاصة لتقسيمها بشكل صحيح:

def get_packed_weights(param, empty_param, device_mesh, rank, dim):
    """
    Interleaves gate and up shards correctly.

    Packed tensor: [G0 G1 G2 G3 | U0 U1 U2 U3]

    With TP=2:
    - Rank 0 gets: [G0 G1 | U0 U1]
    - Rank 1 gets: [G2 G3 | U2 U3]
    """

يُخزَّن تشغيل TP في WeightTransform ويُطبَّق بعد عمليات التحويل:

if matched_tp_pattern := tp_plan_alt.search(renamed_key):
    tp_layer = ALL_PARALLEL_STYLES[model.tp_plan[matched_tp_pattern]]
    mapping.distributed_operation = tp_layer(
        device_mesh=device_mesh,
        rank=device_mesh.get_local_rank(),
        empty_param=empty_param.clone()
    )

تكامل التكميم

تتداخل عملية التكميم مع خط تحميل النموذج بطريقتين، وذلك اعتمادًا على ما إذا كان نقطة الحفظ (checkpoint) مُكمَّمةً بالفعل:

  • نقاط التفتيش المُكمّاة مسبقًا: يوفّر المُكمّاة أمثلة من WeightConverter (عبر get_weight_conversions()) لفكّ تسلسل الموترات المُكمّاة. يتم الحفاظ على أنواع البيانات (dtypes) في نقاط التفتيش لتجنّب التحويلات غير المرغوبة.
  • التكميم الفوري: يوفّر مُكمِّم عملية تكميم تُطبَّق بعد عمليات التحويل، حيث يتم تكميم الأوزان أثناء تحميلها.

يمكن أيضًا للمكمِّم إعادة كتابة قائمة التحويل بأكملها في نهاية get_model_conversion_mapping عبر update_weight_conversions(...) — على سبيل المثال، يضيف مكمِّم FP8 عملية Fp8Dequantize في بداية كل محوِّل موجود، بحيث تُطبَّق مقاييس كل كتلة قبل أن تُسطِّح عمليات دمج الخبراء / الربط البنيوى الخاص بكل خبير.

تحميل النماذج بسرعة وكفاءة

يكون تحميل النموذج أسرع ويستهلك ذاكرة أقل لأنّ المُحمِّل يعرف أيّ المتجهات (tensors) مطلوبة للعمليات، ويجدول تجسيدها (materialization) بشكل كسول (lazy).

يفحص المُحمِّل نقطة التفتيش مرة واحدة لاكتشاف التطابقات مع الأنماط وجمع المتجهات. يخزّنها ككائنات Future ويقدّمها إلى مجموعة خيوط (thread pool) للتحميل غير المتزامن دون تعطيل GIL. يبدأ تحميل المعامل فور توفّر خيط متاح له.

إذا كان نظامك يشغّل عمليات أخرى كثيفة، فقد تُبطئ الخيوط المتعددة عملية التحميل بدلاً من تسريعها. في هذه الحالة، اضبط متغير البيئة HF_DEACTIVATE_ASYNC_LOAD=1 لتحميل الأوزان بشكل تسلسلي.

الافتراضي هو 4 خيوط لتحميل المعاملات بشكل غير متزامن. وهذا يوفر أفضل توازن بين سيناريوهات التحميل والأجهزة. العمل يعتمد بشكل رئيسي على الإدخال/الإخراج، ولكن اعتمادًا على عتاد المعالج والـdtype المطلوب عند التحميل، قد يصبح محكومًا بالمعالج/وحدة معالجة الرسوميات إذا كان الـdtype مختلفًا عن ذلك المُخزَّن (وهذا يتطلب عملية نسخ إضافية).

التحميل غير المتزامن مقابل المتزامن

def spawn_materialize(thread_pool, tensor, device, dtype) -> Future | Callable:
    def _job():
        return _materialize_copy(tensor, device, dtype)

    if thread_pool is not None:
        return thread_pool.submit(_job)  # Async: returns Future
    else:
        return _job  # Sync: returns Callable (deferred execution)

يُستخدم التحميل المتزامن في الحالات التالية:

  • يتم تعيين متغير البيئة HF_DEACTIVATE_ASYNC_LOAD=1.
  • تم تفعيل تفريغ القرص (تتطلب قيود الذاكرة التحميل التسلسلي).
  • يتم تفعيل التكميم الفوري (لتجنب تقدّم خيوط العمل على خطوة التكميم).

تدفق المادي

1. Checkpoint iteration (Phase 1):
   - For each key, walk the transform list once
   - Submit materialization job to ThreadPoolExecutor
   - Job returns Future (async) or Callable (sync)
   - Collect into the matching WeightConverter / WeightRenaming

2. Per-mapping processing (Phase 2, one mapping at a time):
   - materialize_tensors() waits for this mapping's Futures only
   - Apply conversion operations chain (self.operations)
   - Apply quantization operation (if on-the-fly)
   - Set parameters on model
   - Delete realized tensors immediately

3. Cleanup:
   - Thread pool shutdown (with cancel_futures=True for interrupts)

كفاءة الذاكرة

عند تحويل وزن، ينتظر المحوّل حتى تتجسّد جميع الموترات المطلوبة إذا لم تكن قد تم تحميلها بعد. على سبيل المثال، تتطلب عملية MergeModulelist تحميل جميع الأوزان داخل ModuleList قبل الدمج.

يتطلب دمج الموترات (tensors) نسخة مؤقتة، لذا فإن عمليات مثل MergeModulelist و Concatenate تحتاج إلى ضعف ذاكرة الموترات الأساسية أثناء التحويل. بعد الدمج، يبقى الموتر الناتج فقط في الذاكرة. ذروة الذاكرة النظرية في أسوأ الحالات هي حجم النموذج بالإضافة إلى الموترات المطلوبة لأكبر عملية MergeModulelist أو Concatenate.

لا يحدث هذا الأسوأ إلا عندما تكون جميع المعلمات الأخرى قد اكتمل تحميلها قبل بدء عملية التحويل المكثفة. هناك سيناريوهان يحفزان هذا الوضع.

  1. جميع المعاملات التي تم تحميلها بشكل غير متزامن قبل بدء عملية التحويل المكثفة (كانت مجموعة الخيوط أسرع من طابور التحويل).
  2. التحويل المتطلب هو الأخير.

على سبيل المثال، في نموذج MoE يستخدم MergeModulelist للخبراء في كل طبقة، يكون ذروة الذاكرة النظرية في أسوأ الحالات هي حجم النموذج مضافًا إليه الخبراء في طبقة واحدة.

هذه السيناريوهات الأسوأ نادرة الحدوث. تميل ذروة الذاكرة الفعلية إلى البقاء قريبة من حجم النموذج.

قابلية العكس

يدعم النظام حفظ النماذج مع التحويلات العكسية، مما يتيح عملية حفظ/تحميل ذهاباً وإياباً. يتم تنفيذ الحفظ في مرحلتين: أولاً المحوِّلات العكسية (حيث يتطابق كل متجه مع محوِّل واحد على الأكثر)، ثم إعادة التسمية العكسية، وفقاً لقاعدة الترتيب المذكورة أعلاه.

def revert_weight_conversion(model, state_dict):
    """Applies reverse conversions for saving."""
    weight_conversions = getattr(model, "_weight_conversions", None)

    # Reverse all transforms
    reverse_weight_conversion = [
        conversion.reverse_transform() for conversion in weight_conversions
    ]

    # Apply in reverse
    for first_param_name, reversed_converter in conversion_mapping.items():
        realized_value = reversed_converter.convert(first_param_name, model=model)

يتم تخزين قائمة التحويلات المستخدمة عند التحميل على النموذج باسم _weight_conversions (حيث تُحفظ فقط العناصر التي تم تفعيلها فعليًا، مما يضمن إعادة تطبيق إعادة التسميات غير الثنائية مثل PrefixChange بشكل متماثل بشكل صحيح). إذا تم إنشاء النموذج دون استخدام from_pretrained (وبالتالي لا يحتوي على _weight_conversions)، فإن revert_weight_conversion يلجأ إلى إعادة حساب التحويل عبر get_model_conversion_mapping مع استبعاد أي PrefixChange منه (لأننا لا نستطيع معرفة ما إذا كان نقطة الفحص الأصلية تحتوي على البادئة).

قد تحتوي الأنماط المستهدفة على عناصر تعبيرات نمطية تحتاج إلى معالجة للاتجاه العكسي:

def process_target_pattern(pattern: str) -> tuple[str, str | None]:
    """
    - Removes `^` and `$` anchors
    - Removes negative lookahead/lookbehind
    - Detects capturing groups, replaces with \1
    """

أمثلة واقعية

نمط Mixtral من MoE

تنسيق نقطة التفتيش:

model.layers.0.block_sparse_moe.experts.0.w1.weight  # gate per expert
model.layers.0.block_sparse_moe.experts.0.w2.weight  # down per expert
model.layers.0.block_sparse_moe.experts.0.w3.weight  # up per expert
...
model.layers.0.block_sparse_moe.experts.7.w1.weight

تنسيق النموذج:

model.layers.0.mlp.experts.gate_up_proj  # (8, 4096, 28672)
model.layers.0.mlp.experts.down_proj     # (8, 14336, 4096)

خريطة التحويل (من conversion_mapping.py):

"mixtral": [
    WeightRenaming(".block_sparse_moe.", ".mlp."),
    WeightConverter(
        source_patterns=[".experts.*.w1.weight", ".experts.*.w3.weight"],
        target_patterns=".experts.gate_up_proj",
        operations=[MergeModulelist(dim=0), Concatenate(dim=1)],
    ),
    WeightConverter(
        source_patterns=[".experts.*.w2.weight"],
        target_patterns=".experts.down_proj",
        operations=[MergeModulelist(dim=0)],
    ),
],

نموذج رؤية ولغة مركّب (Gemma3)

يتميز Gemma3 بالتكوين القياسي المتمثل في "منطق بادئة مختلف لنموذج الرأس مقارنةً بالنموذج الأساسي". يتم تسجيل إدخalin (هنا عبر مستعرات تشير إلى قوائم "llava" / "LlavaModel" المشتركة):

# model_type: applies to Gemma3ForConditionalGeneration / Gemma3ForSequenceClassification
"gemma3": "llava"           # → adds "model." in front of language_model / vision_tower / ...

# class:    applies to the inner Gemma3Model only
"Gemma3Model": "LlavaModel"  # → minimal rename inside the already-prefixed namespace

مسار DFS لـ Gemma3ForConditionalGeneration:

  1. الجذر — تفشل عملية البحث عن الفئة (Gemma3ForConditionalGeneration غير مسجّلة)، بينما تنجح عملية البحث عن model_type ("gemma3") → تُضاف عمليات إعادة الكتابة التي تبدأ ببادئة "llava" بدون نطاق، مما يضع language_model وvision_tower وmulti_modal_projector تحت model.*.
  2. النموذج الداخلي model: Gemma3Model — إصابات البحث عن الفئة (Gemma3Model → LlavaModel) → يُطبَّق فقط إسناد LlavaModel، محصور النطاق على "model". عمليات إعادة التسمية الأوسع بكثير للمنتصف "gemma3" (="llava") لا تُعاد تطبيقها هنا، وهذا بالضبط ما تريده: النموذج الداخلي موجود في مساحة أسماء حيث يكون المنتصف صحيحًا بالفعل.

تُستخدم نفس بنية الزوج في جميع نماذج VLM من عائلة Llava (PaliGemma، InternVL، Mistral3، …). يتولى إدخال model_type إجراء التعديل على بادئة نموذج الرأس؛ بينما يحافظ إدخال الفئة على خرائط النموذج الأساسي الداخلي بأقل قدر ممكن.

تضمين أعمق مع تجاوز محدد للرأس (DETR)

يُظهر DetrForSegmentation تجاوزًا محددًا للرأس (head) مُطبَّقًا فوق مستويين متداخلين:

DetrForSegmentation                  (class registered: segmentation-only renames)
├── detr: DetrForObjectDetection     (no mapping; just walked through)
│   └── model: DetrModel             (class registered: shared base transforms)
│       └── backbone, encoder, ...
└── mask_head, bbox_attention        (head-specific weights)

يُشترك في ذلك خريطتان (واحدة لكل مفتاح مسجّل):

"DetrModel":           [WeightRenaming("backbone.conv_encoder", "backbone"), ...]      # shared base
"DetrForSegmentation": [WeightRenaming("mask_head.lay1", "mask_head.conv1.conv"), ...] # head-specific

مسار DFS:

  1. يتطابق الجذر DetrForSegmentation حسب الفئة → تمت إضافة إعادة تسمية التقطيع unscoped.
  2. detr: DetrForObjectDetection غير مسجّل → لم تُضَف أي تحويلات؛ يستمر DFS في التعمق بداخله.
  3. يتطابق detr.model: DetrModel عبر الفئة → تُضاف التحويلات الأساسية مع scope_prefix="detr.model".

ولهذا السبب لا يحتاج DetrForObjectDetection إلى تعيين خاص به: فالتعيين المسجّل الوحيد في شجرة فرعيه (DetrModel) يُطبَّق تلقائيًا على المسار الصحيح.

تعيد مستعرات مفاتيح الفئة استخدام الترميز الأساسي دون تسجيل إضافي: "MaskFormerDetrDecoder": "DetrModel" يجعل مُفكّك MaskFormer يتبنّى نفس التحويلات تحت اسم فئته الخاصة.

عمليات مخصصة (ERNIE 4.5 VL MoE)

عندما لا تكون العمليات المدمجة كافية، يمكنك إنشاء فئة فرعية مخصصة من ConversionOps. على سبيل المثال، يحتاج نموذج ERNIE 4.5 VL MoE إلى تقسيم قائمة الخبراء المشتركة بين نمطي النص والرؤية — وهو أمر لا تتكفّل به أي عملية مدمجة واحدة. تقوم العملية المخصصة ErnieFuseAndSplitTextVisionExperts بتقسيم الخبراء وإعادة تجميعهم عبر مفتاحين مستهدفين:

"ernie4_5_vl_moe": [
    WeightRenaming("vision_model", "vision_tower"),
    WeightConverter(
        source_patterns=["experts.*.down_proj.weight"],
        target_patterns=[
            "text_moe.experts.down_proj",
            "vision_moe.experts.down_proj",
        ],
        operations=[ErnieFuseAndSplitTextVisionExperts(stack_dim=0, concat_dim=1)],
    ),
],

يجب أن تنفّذ العمليات المخصّصة convert() وخاصية reverse_op لدعم حفظ/تحميل ذهاباً وإياباً.

أسماء مستعارة لأنواع النماذج

تشارك العديد من النماذج أنماط تحويل مشتركة:

_MODEL_TO_CONVERSION_PATTERN = {
    "mixtral": "mixtral",
    "minimax": "mixtral",
    "qwen2_moe": "qwen2_moe",
    "deepseek_v2": "qwen2_moe",
    "deepseek_v3": "qwen2_moe",
    "qwen3_moe": "qwen2_moe",
    "olmoe": "qwen2_moe",
    ...
}

إعادة استخدام مكونات التحميل الديناميكي

لا يقتصر تحميل الأوزان الديناميكي على نقاط التفتيش الكاملة للنموذج. فبفضل نفس المكونات الأساسية، يمكنك تحميل أي مجموعة من الأوزان، ما دمت قادراً على وصف كيفية خرائط مفاتيح نقطة التفتيش إلى المعاملات، والتأكد من وجود الوحدات المستهدفة.

على مستوى عالٍ، يبدو العقد على النحو التالي:

  1. جهّز مساحة أسماء النموذج. تأكد من وجود الوحدات/المعاملات التي تريد تحميلها وأن أسماءها مطابقة لما تستهدفه خريطتك. وفيما يتعلق بالمُكيّفات (adapters)، يعني ذلك استدعاء inject_adapter_in_model(...) حتى توجد وحدات المُكيّف قبل عملية التحميل. أما بالنسبة للرؤوس المخصصة أو الوحدات الإضافية، فقم بإنشائها على النموذج أولاً.
  2. Describe how to map weights. أنشئ قائمة تحويل/إعادة تسمية (على سبيل المثال، في دالة مساعدة مثل _build_peft_weight_mapping(...)) باستخدام WeightConverter أو WeightRenaming. هنا هو المكان الذي تُعبّر فيه عن كيفية تحويل مفاتيح نقطة التفتيش (checkpoint) أو تقسيمها أو دمجها أو إعادة تسميتها لتتوافق مع نطاق اسم النموذج الخاص بك. يمكنك القيام بثلاثة أشياء رئيسية تقريبًا:
    • أضِف العمليات إلى قائمة المحوِّلات: ستُطبَّق هذه العمليات على جميع الأوزان باستثناء تلك التي تم جمعها في أي من WeightConverter. وعادةً ما تكون هذه العمليات من نوع WeightRenaming
    • أضِف عمليات إلى قائمة عمليات كل محوِّل: وهذا ما يحدث في حالة Quantization، حيث نضيف عملية تكميم بعد قائمة عمليات أي WeightConverter.
    • استبدال عمليات / map بعمليات مخصّصة: هذا ما يحدث مع peft. نستبدل عملية Concatenate الخاصة بـ mixtral مثلًا، لتصبح PeftConcatenate (التي تُعرَّف في PEFT). وبهذه الطريقة، عند قراءة نقطة التفتيش الخاصة بالمُكيّف، يتم جمع الأوزان التي يجب دمجها، وتنسيقها بشكل صحيح من أجل peft
  3. Load + finalize + report. استخدم المُحمِّل الأساسي لتنفيذ التحويل وملء المتجهات (tensors)، ثم أكمل العملية وسجّل النتائج. بشكل محدد، يتبع هذا التدفق الخطوات التالية:
    • LoadStateDictConfig(...) + _load_pretrained_model(...) لتحميل النموذج وتحويله.
    • _finalize_load_state_dict(...) لنقل أي تينسورات مفقودة أو غير متطابقة من meta، وتبديئتها، وربط الأوزان.
    • log_state_dict_report(...) للإبلاغ عن المفاتيح المفقودة/غير المتوقعة/غير المتطابقة (وأخطاء التحويل).

تُتاح هذه الواجهات البرمجية (APIs) لتمكينك من التعامل مع أكواد مخصصة وتنسيقات أوزان مخصصة، مع ضمان استفادتك في الوقت نفسه من تحميل الأوزان الأكثر كفاءةً وتقسيمها وجودة تجربة الاستخدام العالية في واجهة transformers البرمجية!

مرجع الملفات الرئيسية

ملفالغرض
src/transformers/core_model_loading.pyمنطق التحميل الأساسي، WeightConverter، WeightRenaming، ConversionOps
src/transformers/conversion_mapping.pyالخرائط المدمجة وتكوين فرعي النموذج (get_model_conversion_mapping)
src/transformers/integrations/tensor_parallel.pyفئات ومساعدات تقسيم TP
src/transformers/quantizers/base.pyخطافات التكميم والفئة الأساسية
Update on GitHub