Transformers documentation
التوازي في الموترات للاستدلال
التوازي في الموترات للاستدلال
يقوم توازي الموترات بتقسيم طبقة النموذج إلى أجزاء بحيث تعمل عدة معجلات أجهزة عليها في وقت واحد. يتيح لك ذلك تشغيل نماذج تتجاوز سعة ذاكرة وحدة معالجة رسومية واحدة وتحقيق معدل إنتاجية أعلى. ستحتاج إلى اتصال سريع داخل العقدة لأن وحدات معالجة الرسومات تتبادل النتائج الجزئية في كل طبقة.
يدعم النموذج توازي الموترات إذا عرّفت إعداده الخاصية base_model_tp_plan. يمكنك التحقق من نموذج محمّل باستخدام الخاصية supports_tp_plan.
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
print(model.supports_tp_plan)يتناول هذا الدليل تفعيل توازي الموترات (Tensor Parallelism) في مكتبة Transformers والاستراتيجيات المتاحة لتقسيم الموارد.
تقسيم النموذج
قم بتكوين عدد أجهزة التوازي المجهري باستخدام tp_size في DistributedConfig.
- اضبط
DistributedConfig(tp_size=N)لاستخدام الخطة المحددة مسبقًا للنموذج. - حدّد
tp_planيدويًا وقم بتمريره إلى DistributedConfig معtp_size.
يمكنك أيضاً تعيين tp_plan="auto" لطلب الخطة المحددة مسبقاً بشكل صريح. عند إغفال tp_size وتعيين tp_plan، يُشتق tp_size من WORLD_SIZE مقسوماً على أحجام التوازي الأخرى. تمرير tp_plan مباشرة إلى from_pretrained() أصبح غير موصى به وسيُزال في الإصدار v5.18.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, DistributedConfig
# model_id = "meta-llama/Llama-4-Scout-17B-16E-Instruct" # better to visualize all the possible strategies
distributed_config = DistributedConfig(tp_size=4)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-8B-Instruct",
dtype=torch.bfloat16,
distributed_config=distributed_config,
)
print(model.tp_plan)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
prompt = "Can I help"
inputs = tokenizer(prompt, return_tensors="pt").input_ids.to(model.device)
# distributed run
outputs = model(inputs)شغّل سكربت الاستدلال باستخدام torchrun. استخدم عملية واحدة لكل وحدة معالجة رسومية.
torchrun --nproc-per-node 4 demo.py
حدّد خطة التوازي للتensors لكل طبقة في tp_plan وقم بتمريرها عبر DistributedConfig. يستخدم المثال أدناه التقسيم العمودي والصفّي. راجع قسم استراتيجيات التقسيم للاطلاع على الاستراتيجيات المدعومة الأخرى.
يتطلّب التقسيم اليدوي فهماً عميقاً لبنية النموذج وتفاعلات الاستراتيجيات. إن اختيارات التقسيم غير الموفّقة تُنتج نماذج بطيئة تفشل أو تُعطي نتائج غير صحيحة. يشرح Ultra-Scale Playbook استراتيجيات التقسيم بالتفصيل.
المفاتيح هي أسماء وحدات أو معلمات، حيث يمثل * فهارس الطبقات. يثير اسم استراتيجية غير معرّف ValueError يعرض قائمة بالأسماء المدعومة، ويسجّل Transformers تحذيرًا لقواعد الخطة التي لم تتطابق مع أي شيء وللمعلمات التي لم تغطيها أي قاعدة.
from transformers import AutoModelForCausalLM, DistributedConfig
tp_plan = {
"model.layers.*.self_attn.q_proj": "colwise",
"model.layers.*.self_attn.k_proj": "colwise",
"model.layers.*.self_attn.v_proj": "colwise",
"model.layers.*.self_attn.o_proj": "rowwise",
...
}
distributed_config = DistributedConfig(tp_size=4, tp_plan=tp_plan)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Meta-Llama-3-8B-Instruct",
dtype="auto",
distributed_config=distributed_config,
)
print(model.tp_plan)استراتيجيات التقسيم
ALL_PARALLEL_STYLES، وهو ParallelInterface مُعرَّف في distributed/parallel_utils.py، يُقرِن كل اسم استراتيجية يمكن استخدامه في خطة إلى مثيل مُهيَّأ من الاستراتيجية. لا تتفاعل معه مباشرةً لتقسيم نموذج، لكنه القائمة المرجعية للأسماء المتاحة. يسجِّل distributed/tensor_parallel.py استراتيجيات توازي الموترات فيه:
ALL_PARALLEL_STYLES.register("embedding_rowwise", EmbeddingParallel(shard_dim=0))
ALL_PARALLEL_STYLES.register("embedding_colwise", EmbeddingParallel(shard_dim=1))
ALL_PARALLEL_STYLES.register("colwise_gather_output", ColwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("colwise_rep", ColwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("colwise", ColwiseParallel(input_layouts=Replicate(), output_layouts=Shard(-1)))
ALL_PARALLEL_STYLES.register("rowwise", RowwiseParallel(input_layouts=Shard(-1), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("rowwise_split_input", RowwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("rowwise_rep", RowwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("packed_colwise", PackedColwiseParallel())
ALL_PARALLEL_STYLES.register("packed_rowwise", PackedRowwiseParallel())
ALL_PARALLEL_STYLES.register("sequence_parallel", SequenceParallel(use_local_output=True))
ALL_PARALLEL_STYLES.register("replicated_with_grad_allreduce", ReplicatedWithGradAllReduce())
ALL_PARALLEL_STYLES.register("mla_kv_a_proj", MlaKvAProjParallel())
ALL_PARALLEL_STYLES.register("all_reduce", AllReduceParallel())
ALL_PARALLEL_STYLES.register("moe_tp_experts", MoeExpertsParallel())
ALL_PARALLEL_STYLES.register("moe_identity_expert", MoeIdentityParallel())
ALL_PARALLEL_STYLES.register("local_params", LocalParamsParallel())كل استراتيجية للتوازي على مستوى الموترات هي فئة فرعية من TensorParallelLayer في ذلك الملف. أما استراتيجيات التوازي على مستوى الخبراء، مثل ep_dispatch_experts، فتوجد في distributed/expert_parallel.py، وهي فئات فرعية من ExpertParallelLayer، التي تملك نفس الخطافات، وتُسجَّل في ALL_PARALLEL_STYLES نفسها. الاستراتيجيتان اللتان ستلجأ إليهما في الغالب، ColwiseParallel وRowwiseParallel، تقبلان مواضع input_layouts وoutput_layouts، وهو ما يتيح لفئة واحدة تغطية عدة أسماء مخططات. تترك colwise ناتجها مجزأً على البُعد الأخير ليستهلكه طبقة rowwise تالية، بينما تجمع colwise_gather_output الناتج بالكامل مرة أخرى إلى موتر كامل.
تصف الجدول أدناه كل استراتيجية.
| الاستراتيجية | الوصف |
|---|---|
ColwiseParallel | يقسّم وزن الخطي على بُعد الميزة الإخراجية (Shard(0) لوزن nn.Linear ثنائي الأبعاد) ويقسّم انحيازاً أحادي البعد. يعيد توزيع المدخل إلى Replicate()، ثم يضع المخرج في output_layouts. |
RowwiseParallel | يقسّم وزنًا خطيًا على بُعد ميزات الإدخال (Shard(-1)) ويكرر الانحياز، الذي يُضاف بعد عملية الاختزال. ينتج عن كل تمريرة أمامية لكل رتبة مخرجات من نوع Partial() تحتوي على حصتها من المجموع، ثم يعيد transform_output_post_forward توزيعها إلى output_layouts. يؤدي الاختزال إلى Replicate() إلى تنفيذ عملية all-reduce، بينما يؤدي الاختزال إلى Shard(1) إلى تنفيذ عملية reduce-scatter. |
EmbeddingParallel | يقوم بتجزئة nn.Embedding مع تكرار معرّفات الرموز. يؤدي shard_dim=0 (embedding_rowwise، وهو الخيار المعتاد) إلى تجزئة المفردات وتجميع نتائج البحث الجزئية لكل رتبة عبر جميع الرتب للحصول على المخرجات الكاملة. أما shard_dim=1 (embedding_colwise) فيقوم بتجزئة بُعد التضمين وتجميع شريحة المخرجات الخاصة بكل رتبة عبر جميع الرتب. |
PackedColwiseParallel | نسخة من ColwiseParallel مخصصة للأوزان المدمجة، مثل up_proj وgate_proj المُجمَّعة في gate_up_proj. استخدم split_factor عندما يحتوي الوزن على عدد من الطبقات غير اثنين. |
PackedRowwiseParallel | المقابل على مستوى الصفوف، للأوزان المعبأة على طول البُعد الأخير. يكرر المعاملات أحادية البُعد. |
SequenceParallel | ينسخ معاملات الوحدة ويقسّم مدخلاتها على sequence_dim (الافتراضي هو 1). يُستخدم للمعايير التي تعمل لكل رمز، مثل LayerNorm وRMSNorm. |
ReplicatedWithGradAllReduce | ينسخ معاملًا، لكنه يُجري عملية اختزال شامل (all-reduce) على تدرّجه. وهو ضروري للمعايير (norms) التي تقع بين طبقة عمودية وطبقة صفية، وتُطبَّق على محور مجزّأ، حيث لا يرى كل رتبة (rank) سوى رؤوسها (heads) الخاصة. |
AllReduceParallel | يُجري عملية Partial() على مخرجات التقدّم الأمامي للوحدة لتحويلها إلى Replicate(). استخدمه كنقطة مزامنة للوحدة التي ينتهي حسابها بمجموع جزئي. |
MlaKvAProjParallel | يقوم بتقسيم مخرجات kv_a_proj_with_mqa الخاصة باهتمام MLA بنمط DeepSeek-V2، ويخفّض تدرّج نصف RoPE عبر جميع العمليات (all-reduce)، مما يتجاوز kv_b_proj والذي كان سيحتفظ بتدرّج جزئي لولا ذلك. يتطلب وجود qk_rope_head_dim في إعدادات النموذج. |
MoeExpertsParallel | خبراء MoE المتوازيين على مستوى الموترات. يقوم بتجميع (All-reduce) مخرجات الخبراء في الاتجاه الأمامي، ويضيف عمليات التجميع العكسية (backward all-reduces) لحالات الإخفاء وأوزان التوجيه. |
MoeIdentityParallel | يقسم مدخل خبير الصفر أو الهوية على حجم الشبكة مسبقاً، مما يلغي عملية moe_tp_experts التي تُطبّقها المكوّنات اللاحقة. |
LocalParamsParallel | يُنفّذ تمرير الأمامي (forward) للوحدة باستخدام معاملاتها المجزأة الخاصة كمُتجهات محلية. استخدمه على المالك لمُعامل nn.Parameter عاري، مثل مصائد الانتباه (attention sinks)، الذي تجزّئه مدخلته الخاصة في الخطة. |
MoEParamShard | يقوم بتجزئة أوزان خبراء MoE على التوزيع المحدد. يدعم اسم grouped_gemm، حيث يعيد shards_expert_dim=True أيضًا كتابة module.num_experts إلى عدد الخبراء لكل رتبة. |
EpRouterParallel | يُخفي نقاط التوجيه للخبراء غير المحليين ويعيد تعيين المعرّفات العالمية للخبراء إلى معرّفات محلية، بحيث يعمل كل رتبة فقط على الخبراء التي تملكها. يتطلب أن يكون num_experts قابلاً للقسمة على حجم الشبكة. |
RouterParallelMegaMoe | نسخة Router مخصصة لـ DeepGEMM Mega MoE، والتي تُرسل الخبراء داخل النواة (kernel) التي تريد إخراج الـ router دون أي تعديل. |
MoeTensorParalellMegaMoeExperts | طبقة خبراء مخصصة للاستدلال فقط لـ DeepGEMM Mega MoE. تتخطى مزامنة التدرجات وتُمرّر مجموعة العمليات إلى الوحدة حتى يتمكن النواة من إعداد مخازنها المشتركة عند أول تمريرة أمامية. |
استراتيجيات مكدّسة
تقوم حزمة الأوزان بدمج عدة طبقات خطية في طبقة واحدة أكبر. تقسّم استراتيجيتا PackedColwiseParallel وPackedRowwiseParallel الأوزان المحزومة بشكل صحيح. أما استراتيجيتا ColwiseParallel أو RowwiseParallel الأساسيتان فتقسّمان الأوزان المحزومة بشكل غير صحيح.
يجمع المثال أدناه up_proj وgate_proj في وحدة واحدة اسمها gate_up_proj ويتطلب استخدام استراتيجية packed_rowwise لتقسيم gate_up_proj.
class Llama4TextExperts(nn.Module):
...
self.gate_up_proj = nn.Parameter(torch.zeros(self.num_experts, self.hidden_size, 2 * self.expert_dim))استخدم ضرب المصفوفات الدفعية في مسار forward لحساب مخرج وحدة gate_up_proj.
def forward(self, hidden_states):
...
gate_up = torch.bmm(hidden_states, self.gate_up_proj) # Compute the output of the gate_up_proj module
gate, up = gate_up.chunk(2, dim=-1) # Split the output into gate and upيقسم Shard العادي تلك البُعد إلى كتل متتالية، لذا يستلم الرتبة ذيل gate ورأس up بدلاً من شريحة من كلٍّ منهما. أما استراتيجيات الحزم فتستخدم _StridedShard مع split_factor (الافتراضي 2) لتبديل التقسيم، مما يمنح كل رتبة شريحة متطابقة من كلا النصفين حتى تبقى chunk متطابقة بعد التجزئة.
استراتيجيات التقسيم المخصصة
ورث من TensorParallelLayer في distributed/tensor_parallel.py لإنشاء استراتيجية تقسيم مخصصة. تجاوز فقط الخطافات التي تحتاجها استراتيجيتك، إذ أن لكل خطاف قيمة افتراضية لا تؤدي أي إجراء.
| خطاف | الغرض |
|---|---|
validate_param | رفض معلم لا تستطيع هذه الاستراتيجية تجزئته، قبل تحميل أي أوزان. |
shard_param | استبدل أحد المعاملات بموضع احتياطي من نوع DTensor حتى يعرف المُحمِّل أي شريحة تنتمي إلى هذا الرتبة. |
transform_inputs_pre_forward | أعد توزيع مدخلات الوحدة على التخطيط الذي تتوقعه دالة الأمامية. |
context_around_forward | غلّف forward في مدير سياق، على سبيل المثال لتوفير الموترات المحلية للنواة. |
transform_output_post_forward | إعادة توزيع أو تقليل مخرجات الوحدة. |
should_use_local_tensors | Report that this module’s forward needs plain tensors rather than DTensors. |
install_forward | استبدل module.forward بالكامل. لا تتجاوز هذا إلا عندما لا تكون الخطافات المذكورة أعلاه كافية، كما يفعل ReplicatedWithGradAllReduce لتسجيل خطاف للخلف. |
يستعرض المثال أدناه نسخة مختصرة من ColwiseParallel.
ورث من
TensorParallelLayerوخزّن المواضع التي يعمل بها هذا الاستراتيجي. لا يحدد الصنف الأساسي أي__init__، لذا لا يوجد ما يُستدعى عليهsuper().class ColwiseParallel(TensorParallelLayer): def __init__(self, *, input_layouts=None, output_layouts=None, use_local_output: bool = True): self.input_layouts = input_layouts or Replicate() self.output_layouts = output_layouts if output_layouts is not None else Shard(-1) self.use_local_output = use_local_outputنفّذ
shard_paramلتغليف معامل واحد كحامل مؤقت من نوعDTensor. يعمل هذا على متجهات meta، لذا يبنيdistribute_tensorالبيانات الوصفية فقط دون نقل أي بيانات. مرّرsrc_data_rank=Noneلأنه لا يوجد متجه كامل بعد يمكن التشتت منه.def shard_param(self, module, param, mesh): meta = module._parameters.get(param) if meta is None: return # Output features live on dim 0 for a 2D weight and dim -1 for a 1D bias placement = Shard(1) if isinstance(module, torch.nn.Embedding) else Shard(meta.ndim - 2) module._parameters[param] = torch.nn.Parameter( distribute_tensor(meta, mesh, [placement], src_data_rank=None), requires_grad=meta.requires_grad, )نفّذ تحويلات الإدخال والإخراج. يستدعي
install_forwardهذه التحويلات قبل وبعد دالة forward الأصلية للوحدة، لذا لا يتعين عليها سوى نقل المصفوفات (tensors) بين التخطيطات. يفترض التقسيم على مستوى الأعمدة أن يكون الإدخال مكرراً (replicated) وينتج مخرجات مقسمة على البُعد الأخير.def transform_inputs_pre_forward(self, module, args, kwargs, mesh): x = args[0] if not isinstance(x, DTensor): x = DTensor.from_local(x, mesh, [self.input_layouts], run_check=False) if x.placements != (Replicate(),): x = x.redistribute(placements=[Replicate()]) return (x,) + args[1:], kwargs def transform_output_post_forward(self, module, output, mesh): if not isinstance(output, DTensor): output = DTensor.from_local(output, mesh, [Shard(-1)], run_check=False) if output.placements != (self.output_layouts,): output = output.redistribute(placements=[self.output_layouts]) return output.to_local() if self.use_local_output else outputيضيف
ColwiseParallelالمُرفق مسارات سريعة فوق هذا، تتخطى رحلةDTensorذهاباً وإياباً للاستدلال على وحداتnn.Linearالعادية، وللموديولات المُكمّاة التي تحتاج إلى متجهات محلية. اقرأ الكود المصدري قبل نسخه إذا كانت استراتيجيتك تحتاج إلى المعالجة نفسها.سجّل الاستراتيجية حتى يتمكن
tp_planمن الإشارة إليها بالاسم. يتطلب التسجيل نسخة من الاستراتيجية، لذا تُحفظ المواضع التي تمررها إلى__init__مع الاسم. لاحظ()أدناه.import torch from transformers import AutoModelForCausalLM, DistributedConfig from transformers.distributed.parallel_utils import ALL_PARALLEL_STYLES ALL_PARALLEL_STYLES.register("colwise_custom", ColwiseParallel()) tp_plan = { "model.layers.*.self_attn.q_proj": "colwise_custom", ... } distributed_config = DistributedConfig(tp_size=4, tp_plan=tp_plan) model = AutoModelForCausalLM.from_pretrained( "meta-llama/Meta-Llama-3-8B-Instruct", dtype=torch.bfloat16, distributed_config=distributed_config, )
المعايير
يُسهم التوازي على مستوى الموترات بشكل كبير في تسريع الاستدلال، لا سيما عند استخدام أحجام دفع كبيرة أو تسلسلات طويلة.
تُظهر هذه المخطط تسريع الأداء المتوقع لتمريرة أمامية واحدة على Llama مع طول تسلسل يبلغ 512.

تنفيذ التصميم
تنفّذ مكتبة Transformers توازي المصفوفات بطريقة مستقلة عن الإطار البرمجي. وتعتمد على DeviceMesh وDTensor من torch.distributed لتوفير واجهة بسيطة وقابلة للتوسّع.
DeviceMesh
DeviceMesh ينشئ شبكة متعددة الأبعاد من الأجهزة تتواصل فيما بينها. تتطلب استراتيجيات التوازي المختلفة أنماط اتصال مختلفة. أنشئ DeviceMesh بشبكات فرعية متعددة للتعامل مع هذه الأنماط.
import torch
from torch.distributed.device_mesh import init_device_mesh
# Create a 1D mesh of 4 accelerators
device_type = torch.accelerator.current_accelerator().type
device_mesh = init_device_mesh(device_type, (4,), mesh_dim_names=["tp"])تُطبَّق معظم استراتيجيات التوازي في torch.distributed على الشبكة نفسها أو على شبكات فرعية منها. وتتعامل الشبكة تلقائيًا مع أنماط الاتصال.
DTensor
يتعامل DTensor (الموتر الموزّع) مع منطق التوزيع فوق عمليات الموترات المعتادة. تُخزَّن أوزان النموذج تحت توازّي الموترات على شكل DTensors، وهو ما يتيح للإستراتيجية وصف التواصل كتغيير في التخطيط بدلاً من عملية جماعية صريحة.
يُخبر سمة placements مكتبة PyTorch كيفية توزيع متجه (tensor) عبر الأجهزة ضمن DeviceMesh. وتقبل القيم التالية:
يقوم
Shard(dimension)بتقسيمDTensorعبر بُعدٍ محدد علىDeviceMeshالذي تم إنشاؤه في إطاره. يؤدي التقسيم العمودي إلى تجزئة بُعد الميزات الإخراجية للوزن والبُعد الوحيد للانحياز.weight = DTensor.from_local(weight, device_mesh["tp"], placements=[Shard(0)]) # Shard the output features bias = DTensor.from_local(bias, device_mesh["tp"], placements=[Shard(-1)]) # Shard the ONLY dimensionيقوم التقسيم على مستوى الصفوف بتجزئة بُعد الميزات المدخلة بدلاً من ذلك، مع تكرار الانحياز، لأن الانحياز يُضاف مرة واحدة بعد عملية الاختزال وليس على كل رتبة.
weight = DTensor.from_local(weight, device_mesh["tp"], placements=[Shard(-1)]) # Shard the input features bias = DTensor.from_local(bias, device_mesh["tp"], placements=[Replicate()]) # Replicate bias across all GPUsReplicate()يكررDTensorعبرDeviceMesh، مما ينشئ نسخة كاملة من الموتر على كل جهاز.يُشير
Partial()إلى أن المصفوفة (Tensor) بانتظار عملية اختزال (Reduction). يكون ناتج الدالة الأمامية لطبقة صفية (Row-wise) من نوعPartial()، وإعادة توزيعه إلىReplicate()هي ما يُصدر عملية الاختزال الشامل (All-reduce).
الموارد
توفر قسم Ultra-Scale Playbook الخاص بالتوازي على مستوى المصفوفات مزيدًا من التفاصيل.
راجع دليل التوازي بين الخبراء إذا كنت تستخدم نموذجاً من نوع مزيج الخبراء (MoE). تدعم هذه النماذج التوازي على مستوى المصفوفات والتوازي بين الخبراء.
اقرأ منشور المدونة التوازي على مستوى المصفوفات (TP) في Transformers: خمس دقائق لفهمه للحصول على نظرة عامة سريعة على التوازي على مستوى المصفوفات، وتعلّم كيف تختلف إعدادات التوازي العمودي والأفقي.
اطّلع على دليل التدريب Tensor parallelism لمعرفة كيفية استخدامه في إعداد التدريب.