← العودة إلى قائمة المقالات
August 14, 2026
5 دقائق للقراءة

Model Pruning for Low-Latency Trading Inference

Model Pruning for Low-Latency Trading Inference
#model-compression
#pruning
#lottery-ticket
#latency
#deployment

تنهي مقالة DeepLOB قسم النشر بثلاث نقاط — ONNX مع TensorRT، وتكميم INT8، وFPGA — من دون معالجة أي منها. هذه المقالة هي المعالجة المفقودة للمشكلة الأولى الكامنة تحتها جميعًا: النموذج أكبر مما يحتاج إليه. يزيل تقليم الشبكات العصبية المعلمات الزائدة، والادعاء المثير في الأدبيات ليس أن هذا يوفر الذاكرة، بل أن شبكة فرعية تحتفظ بـ 10-20% من الأوزان تستطيع مطابقة دقة النموذج الكثيف.

كون الكمون مهمًا أصلًا هو حالة أثبتتها المدونة من قبل — ZigBolt في مسار المراسلة وضريبة IPC مع حساب نقطة التعادل — كما أن نمذجة السبريد تملك بالفعل مفاضلة السريع لكن الأسوأ قليلًا مقابل البطيء والأفضل، مع جدول كامل للمقارنة بين gradient boosting والتعلم العميق يتضمن صف كمون الاستدلال. وما لا تغطيه أي منها هو كيفية تصغير نموذج معين. ومن مراحل حلقة التسعير، استدلال النموذج هو المرحلة الواقعة بالكامل تحت سيطرتنا؛ أما أرجل النقل فمغطاة، بأرقام p50/p95/p99 قابلة لإعادة الإنتاج، في اتصال البيانات للتداول الخوارزمي.

ما هذه المقالة: الرياضيات والكود العامل لتقليم المقدار، والتقليم المنظم، وتقليم المقدار التكراري، وتقليم الحركة، وتقطير المعرفة، والندرة شبه المنظمة 2:4 من NVIDIA، مطبقة على MLP للتداول.

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

ماذا يمنحك التقليم؟

شبكة عصبية مدمجة تلائم طبقة ذاكرة تخزين مؤقت سريعة

القيد هنا هو الحجم. لننظر إلى نموذج متوسط التردد — MLP من أربع طبقات، كل منها تضم 2048 وحدة مخفية، فوق ميزات دفتر الأوامر:

Parameters=dinh+h2(L1)+hdout\text{Parameters} = d_{\text{in}} \cdot h + h^2 \cdot (L - 1) + h \cdot d_{\text{out}}

عند din=100d_{\text{in}} = 100 وh=2048h = 2048 وL=4L = 4 وdout=3d_{\text{out}} = 3، يكون لدينا نحو 12.6 مليون معلمة — أي نحو 48 MB بصيغة float32. تكون L2 عادةً بين 1 و4 MB، لذلك لا تلائمها الأوزان؛ بل تُجلب من مكان أبعد في كل تمريرة أمامية. قلّم 95% منها، فتصل إلى نحو 630K معلمة فعالة و2.4 MB، وهو حجم يلائمها.

ما إذا كان ذلك يتحول إلى زمن فعلي يعتمد على كون النواة مقيدة بالذاكرة، وهذا سؤال في كثافة العمليات الحسابية لا في الحجم. يعمل سلم سرعة محرك الاختبار الرجعي على نموذج السقف (Williams وWaterman وPatterson) من خلال مثال مقاس بدل افتراض عامل عقوبة؛ وينطبق الإطار نفسه هنا، وكذلك الانضباط نفسه: قِس البايتات المنقولة قبل الادعاء بتحقيق تسريع.

أساسيات التقليم

تقليم منظم وغير منظم عبر شبكة عصبية

التقليم غير المنظم

النهج الأبسط: تصفير الأوزان الفردية بناءً على مقدارها. لمصفوفة أوزان WRm×n\mathbf{W} \in \mathbb{R}^{m \times n}، أنشئ قناعًا ثنائيًا M{0,1}m×n\mathbf{M} \in \{0, 1\}^{m \times n} بحيث:

Mij={1if Wijτ0if Wij<τM_{ij} = \begin{cases} 1 & \text{if } |W_{ij}| \geq \tau \\ 0 & \text{if } |W_{ij}| < \tau \end{cases}

حيث إن τ\tau عتبة مختارة لتحقيق مستوى الندرة المطلوب ss:

s=1M0mns = 1 - \frac{\|\mathbf{M}\|_0}{m \cdot n}

المصفوفة المقلّمة هي W=WM\mathbf{W}' = \mathbf{W} \odot \mathbf{M}، حيث إن \odot هو حاصل ضرب هادامارد. والفكرة أن الأوزان القريبة من الصفر لا تسهم كثيرًا في مخرج الطبقة.

المشكلة، بصياغة واضحة لأن من السهل إساءة قراءة رقم الندرة: الندرة غير المنظمة لا تتحول إلى تسريع على العتاد القياسي. فمصفوفة تحتوي على 90% من الأصفار لا تزال تصدر العدد نفسه من عمليات الضرب والتراكم ما لم تنتقل إلى نوى متناثرة أو عتاد يدعم الندرة. عندما يطبع الكود أدناه Sparsity: 90.0%، فهذا عد للأصفار — وليس عشرة أضعاف لأي شيء، وعلى GEMM كثيف في CPU ليس حتى 1.01 ضعفًا. المساران اللذان يشتريان وقتًا فعلًا هما التقليم المنظم (مصفوفات أصغر) والندرة شبه المنظمة 2:4 (دعم عتادي)، وكلاهما أدناه.

التقليم المنظم

يزيل التقليم المنظم عصبونات أو قنوات أو رؤوس انتباه كاملة. لطبقة خطية y=Wx+b\mathbf{y} = \mathbf{W}\mathbf{x} + \mathbf{b} مع WRm×n\mathbf{W} \in \mathbb{R}^{m \times n}، يؤدي حذف العصبون jj إلى تصفير الصف jj من W\mathbf{W} والعنصر jj من b\mathbf{b}:

importance(j)=Wj,:2=k=1nWjk2\text{importance}(j) = \|\mathbf{W}_{j,:}\|_2 = \sqrt{\sum_{k=1}^{n} W_{jk}^2}

تذهب العصبونات ذات أصغر معيار 2\ell_2 أولًا. هذه هي النسخة التي تنتج مصفوفات أصغر حقًا — لكن فقط إذا أعدت بناء الطبقات فعليًا. فتصفير الصفوف وترك الموتر بحجمه الأصلي لا يغير شيئًا في عدد FLOPs؛ أما خطوة إعادة البناء في قسم التنفيذ فهي التي تحول القناع إلى مصفوفة 512×n512 \times n.

بالنسبة إلى الطبقات الالتفافية، النظير هو تقليم المرشحات. لمصفوفة WRCout×Cin×k×k\mathbf{W} \in \mathbb{R}^{C_{\text{out}} \times C_{\text{in}} \times k \times k}، تكون أهمية مرشح الخرج ii:

importance(i)=Wi,:,:,:F=c,h,wWi,c,h,w2\text{importance}(i) = \|\mathbf{W}_{i,:,:,:}\|_F = \sqrt{\sum_{c,h,w} W_{i,c,h,w}^2}

ويؤدي حذف المرشح ii إلى إزالة قناة خرج كاملة، فيقلل FLOPs تناسبيًا.

فرضية تذكرة اليانصيب

شبكة فرعية فائزة صغيرة معزولة داخل شبكة عصبية شاسعة

في عام 2019، قدم Frankle وCarbin فرضية تذكرة اليانصيب (LTH): توجد داخل شبكة كثيفة مهيأة عشوائيًا شبكة فرعية متناثة — "تذكرة فائزة" — إذا دُربت انطلاقًا من تهيئتها الأصلية، فإنها تطابق دقة الشبكة الكاملة في عدد مماثل من التكرارات.

صياغةً، لنعتبر f(x;θ0)f(\mathbf{x}; \boldsymbol{\theta}_0) مهيأة بـ θ0Dθ\boldsymbol{\theta}_0 \sim \mathcal{D}_\theta. بعد التدريب حتى التقارب نحصل على θ\boldsymbol{\theta}^* ونشتق قناع تقليم m\mathbf{m}. وتنص LTH على وجود m\mathbf{m} بحيث:

f(x;mθ0)trainθmwhereacc(θm)acc(θ)f(\mathbf{x}; \mathbf{m} \odot \boldsymbol{\theta}_0) \xrightarrow{\text{train}} \boldsymbol{\theta}_m^* \quad \text{where} \quad \text{acc}(\boldsymbol{\theta}_m^*) \geq \text{acc}(\boldsymbol{\theta}^*)

مع m0θ0\|\mathbf{m}\|_0 \ll |\boldsymbol{\theta}_0|. أجريت التجارب الأصلية على MNIST وCIFAR-10، حيث احتفظت التذاكر الفائزة بـ 10-20% من المعلمات. ولا ينتقل شيء من ذلك إلى بيانات دفتر الأوامر بالافتراض — فميزات LOB غير ساكنة، والتسمية تكاد تكون ضجيجًا، وهذا نظام مختلف عن تصنيف الصور في الطرق التي قد تكون مهمة بالضبط.

تقليم المقدار التكراري (IMP)

تُعثر على التذكرة بواسطة IMP:

  1. هيئ الشبكة باستخدام θ0\boldsymbol{\theta}_0.
  2. دربها حتى التقارب لتحصل على θ\boldsymbol{\theta}^*.
  3. قلّم p%p\% من الأوزان ذات أصغر مقدار، وأنشئ القناع m\mathbf{m}.
  4. أعد الأوزان الباقية إلى قيمها في θ0\boldsymbol{\theta}_0 (إرجاع للخلف).
  5. كرر من الخطوة 2 باستخدام الشبكة المقنّعة.

في كل جولة يُقلّم الكسر pp (عادةً 20%)، لذلك يبقى بعد nn جولات (1p)n(1-p)^n من المعلمات. وبعد 10 جولات عند p=0.2p = 0.2 يبقى نحو 10.7%.

ثلاث فرضيات حول نماذج التداول، لم تُختبر أي منها

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

  1. الإشارات المالية متناثرة. معظم لقطة دفتر الأوامر ضجيج، لذلك قد تكون الشبكة الفرعية المتناثرة متوافقة طبيعيًا مع إشارة متناثرة. وهذا قابل للاختبار: قارن IMP بقناع عشوائي ذي الندرة نفسها؛ فإذا كانت الندرة هي التي تؤدي العمل، فلا ينبغي للقناع العشوائي أن يتأخر كثيرًا.
  2. التذاكر الفائزة تعمم عبر الأنظمة. هذه دعوى تجريبية عن أسواق لا يوجد خلفها استشهاد، وهي الأكثر إثارة بين الثلاث. اختبرها مباشرة مقابل تسميات الأنظمة من اكتشاف الأنظمة مع HMMs: اعثر على التذكرة في النظام A، وأعد تدريبها في النظام B، وقارنها بتذكرة عُثر عليها أصلًا في B.
  3. الندرة تنظم النموذج. قد تقلل السعة الفعالة من الملاءمة لضجيج البنية المجهرية — وهو ما سيظهر في كون فجوة النموذج المقلّم خارج العينة أصغر من فجوة النموذج الكثيف، لا مجرد مماثلتها.

التنفيذ: تقليم MLP للتداول

طبقات عصبية تتحول عبر قناع متناثر دقيق

النموذج الأساسي

import torch
import torch.nn as nn
import torch.nn.utils.prune as prune
from copy import deepcopy

class TradingMLP(nn.Module):
    """MLP for mid-price direction prediction from order book features."""
    def __init__(self, input_dim=100, hidden_dim=2048,
                 num_layers=4, output_dim=3):
        super().__init__()
        layers = []
        dims = [input_dim] + [hidden_dim] * (num_layers - 1) + [output_dim]
        for i in range(len(dims) - 1):
            layers.append(nn.Linear(dims[i], dims[i + 1]))
            if i < len(dims) - 2:
                layers.append(nn.BatchNorm1d(dims[i + 1]))
                layers.append(nn.ReLU())
                layers.append(nn.Dropout(0.1))
        self.network = nn.Sequential(*layers)

    def forward(self, x):
        return self.network(x)

    def count_parameters(self):
        return sum(p.numel() for p in self.parameters())

model = TradingMLP(input_dim=100, hidden_dim=2048,
                   num_layers=4, output_dim=3)
print(f"Total parameters: {model.count_parameters():,}")

التقليم غير المنظم بالمقدار

def apply_unstructured_pruning(model, sparsity=0.9):
    """Apply global unstructured L1 pruning to all Linear layers."""
    parameters_to_prune = []
    for name, module in model.named_modules():
        if isinstance(module, nn.Linear):
            parameters_to_prune.append((module, 'weight'))

    prune.global_unstructured(
        parameters_to_prune,
        pruning_method=prune.L1Unstructured,
        amount=sparsity,
    )
    return model

def compute_sparsity(model):
    """Fraction of zero weights. Note: a *count*, not a speedup."""
    total, zeros = 0, 0
    for name, param in model.named_parameters():
        if 'weight' in name:
            total += param.numel()
            zeros += (param == 0).sum().item()
    return zeros / total if total > 0 else 0

pruned_model = apply_unstructured_pruning(deepcopy(model), sparsity=0.9)
print(f"Sparsity: {compute_sparsity(pruned_model):.1%}")

التقليم المنظم، مع إعادة البناء التي تجعله حقيقيًا

تصفير الصفوف نصف العمل. أما النصف الذي ينتج التسريع فهو إعادة بناء كل طبقة بحجمها المصغر — وهذا يعني تمرير الحذف إلى الأمام: إسقاط الصف jj من الطبقة ii يسقط أيضًا العمود jj من الطبقة i+1i+1 وقناة jj من أي BatchNorm1d بينهما.

def apply_structured_pruning(model, fraction=0.75):
    """Mask entire neurons by L2-norm of their weight rows."""
    for name, module in model.named_modules():
        if isinstance(module, nn.Linear) and module.out_features > 10:
            prune.ln_structured(
                module, name='weight', amount=fraction, n=2, dim=0
            )
    return model

def rebuild_pruned_mlp(model):
    """
    Physically shrink a structurally pruned TradingMLP.

    Walks the Sequential once. For each Linear: drop the input columns
    the previous layer no longer emits, then drop its own dead output
    rows. BatchNorm1d channels follow the preceding Linear's survivors.
    """
    new_layers = []
    keep_in = None  # surviving output indices of the previous Linear

    for layer in model.network:
        if isinstance(layer, nn.Linear):
            if prune.is_pruned(layer):
                prune.remove(layer, 'weight')

            W, b = layer.weight.data, layer.bias.data
            keep_out = (W.norm(dim=1) > 0).nonzero(as_tuple=True)[0]
            W = W[keep_out]
            if keep_in is not None:
                W = W[:, keep_in]

            new = nn.Linear(W.shape[1], W.shape[0])
            new.weight.data = W.clone()
            new.bias.data = b[keep_out].clone()
            new_layers.append(new)
            keep_in = keep_out

        elif isinstance(layer, nn.BatchNorm1d):
            new = nn.BatchNorm1d(len(keep_in))
            new.weight.data = layer.weight.data[keep_in].clone()
            new.bias.data = layer.bias.data[keep_in].clone()
            new.running_mean = layer.running_mean[keep_in].clone()
            new.running_var = layer.running_var[keep_in].clone()
            new.num_batches_tracked = layer.num_batches_tracked.clone()
            new_layers.append(new)

        else:  # ReLU, Dropout -- shape-agnostic, reuse as is
            new_layers.append(layer)

    rebuilt = deepcopy(model)
    rebuilt.network = nn.Sequential(*new_layers)
    return rebuilt

هناك شيئان ينبغي فحصهما قبل الوثوق بهذا، بروح بوابات التكافؤ التي يشغلها باقي المدونة:

  • الأحجام. ينبغي أن يظهر rebuilt أبعادًا مخفية عند (1fraction)h(1 - \text{fraction}) \cdot h — أي 512 عند fraction=0.75 وh=2048h = 2048 — وعدد معلمات انخفض تربيعيًا، لأن كلا بعدي المصفوفات الداخلية ينكمشان.
  • المخرجات. في وضع eval() يجب أن يطابق rebuilt(x) مخرج النموذج المقنّع قبل إعادة البناء، ضمن سماحية الفاصلة العائمة وعلى الدفعة نفسها. وإذا لم يفعل، فتمرير الأعمدة خاطئ، وكل رقم لاحق يقيس نموذجًا مختلفًا عما تظن.

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

تقليم المقدار التكراري (البحث عن تذكرة اليانصيب)

def lottery_ticket_search(model_cls, model_kwargs, train_fn, eval_fn,
                          rounds=10, prune_rate=0.2, device='cpu'):
    """
    Iterative Magnitude Pruning to find a winning ticket.

    Parameters
    ----------
    model_cls : class -- model constructor
    model_kwargs : dict -- constructor arguments
    train_fn : callable -- train_fn(model) trains the model in-place
    eval_fn : callable -- eval_fn(model) returns out-of-sample accuracy
    rounds : int -- number of pruning rounds
    prune_rate : float -- fraction of surviving weights pruned per round
    """
    model_init = model_cls(**model_kwargs).to(device)
    theta_0 = deepcopy(model_init.state_dict())

    mask = {}
    for name, param in model_init.named_parameters():
        if 'weight' in name:
            mask[name] = torch.ones_like(param, dtype=torch.bool)

    results = []

    for round_idx in range(rounds):
        model = model_cls(**model_kwargs).to(device)
        state = deepcopy(theta_0)
        for name in mask:
            state[name] = state[name] * mask[name].float()
        model.load_state_dict(state)

        train_fn(model)

        acc = eval_fn(model)
        surviving = sum(m.sum().item() for m in mask.values())
        total = sum(m.numel() for m in mask.values())
        sparsity = 1.0 - surviving / total

        results.append({
            'round': round_idx,
            'accuracy': acc,
            'sparsity': sparsity,
            'surviving_params': int(surviving)
        })
        print(f"Round {round_idx}: acc={acc:.4f}, "
              f"sparsity={sparsity:.1%}")

        all_weights = []
        for name, param in model.named_parameters():
            if name in mask:
                alive = param.data.abs()[mask[name]]
                all_weights.append(alive.flatten())

        all_weights = torch.cat(all_weights)
        k = int(len(all_weights) * prune_rate)
        if k == 0:
            break
        threshold = all_weights.kthvalue(k).values.item()

        for name, param in model.named_parameters():
            if name in mask:
                mask[name] = mask[name] & (
                    param.data.abs() >= threshold
                )

    return results, mask

تمثل results المادة الخام لمنحنى الندرة مقابل الدقة الذي تدين به هذه المقالة للقارئ. يجب أن تكون eval_fn خارج العينة فعلًا، على تقسيمات مطهرة — فتشغيل IMP مقيم داخل العينة سيقدم منحنى جميلًا لا يعني شيئًا.

قياسه

جبهة كفاءة تجريدية توازن بين الندرة والدقة والكمون

يُقاس الكمون وفق اصطلاح الخط نفسه في بقية المدونة — مع استبعاد الإحماء، وأفضل نتيجة من N، والإبلاغ عن p50/p95/p99 بدل المتوسط — وهذا البروتوكول، مع الكود، موجود في Polars مقابل pandas. وهناك ثلاث نقاط خاصة بالتقليم:

  • قِس النموذج المعاد بناؤه، لا النموذج المقنّع. فالنموذج المقنّع عند حجم دفعة 1 يقيس الشكل الكثيف.
  • أبلغ عن حجم الدفعة. فالدُفعة 1 (حلقة التسعير) والدُفعة 256 (مسح البحث) تقعان على جانبي خط الحد بين المقيد بالذاكرة والمقيد بالحساب، ويساعدهما التقليم بشكل مختلف.
  • أبلغ عن الدقة على التقسيم نفسه، وفي الأفق نفسه، مع ذكر تعريف التسمية. فجدول الكمون دون عمود الدقة المطابق هو حجة لحذف النموذج بالكامل.

تقنيات متقدمة

مسارات متقدمة لضغط الشبكة تتلاقى في نموذج كفء واحد

التقليم مع تقطير المعرفة

بدل التقليم والضبط الدقيق كل على حدة، استخدم النموذج الكثيف الأصلي بوصفه teacher. يقلل student المقلّم مزيجًا من خسارة المهمة وتباعد KL لتوزيع خرج teacher:

L=(1α)Ltask(y,y^s)+αT2DKL(σ(ztT)σ(zsT))\mathcal{L} = (1 - \alpha) \cdot \mathcal{L}_{\text{task}}(\mathbf{y}, \hat{\mathbf{y}}_s) + \alpha \cdot T^2 \cdot D_{\text{KL}}\left(\sigma\left(\frac{\mathbf{z}_t}{T}\right) \| \sigma\left(\frac{\mathbf{z}_s}{T}\right)\right)

حيث إن zt\mathbf{z}_t وzs\mathbf{z}_s هما logit الخاصان بـ teacher وstudent، وTT هي درجة الحرارة، وα\alpha توازن بين الهدفين. يعيد العامل T2T^2 تحجيم gradients التقطير، التي كانت ستنكمش كـ 1/T21/T^2 بدونه.

def distillation_loss(student_logits, teacher_logits, labels,
                      temperature=3.0, alpha=0.5):
    """Combined task + distillation loss."""
    task_loss = nn.CrossEntropyLoss()(student_logits, labels)

    soft_student = nn.functional.log_softmax(
        student_logits / temperature, dim=-1
    )
    soft_teacher = nn.functional.softmax(
        teacher_logits / temperature, dim=-1
    )
    kd_loss = nn.functional.kl_div(
        soft_student, soft_teacher, reduction='batchmean'
    )

    return (1 - alpha) * task_loss + alpha * (temperature ** 2) * kd_loss

تقليم الحركة

بدل التقليم حسب المقدار المطلق، يقلم تقليم الحركة (Sanh وآخرون، 2020) الأوزان التي تتحرك نحو الصفر أثناء التدريب. وتتراكم درجة الأهمية من حاصل ضرب gradient والوزن:

Sij(t)=Sij(t1)+Wij(t)Wij(t1)sign(Wij(t)LWij(t))S_{ij}^{(t)} = S_{ij}^{(t-1)} + \left| W_{ij}^{(t)} - W_{ij}^{(t-1)} \right| \cdot \text{sign}\left(W_{ij}^{(t)} \cdot \frac{\partial \mathcal{L}}{\partial W_{ij}^{(t)}}\right)

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

الندرة المنظمة 2:4 من NVIDIA

تدعم وحدات NVIDIA GPU من Ampere وما بعدها الندرة المنظمة 2:4 في العتاد: من كل أربعة أوزان متجاورة، يجب أن يكون اثنان بالضبط صفرًا.

i,W[4i:4i+4]0=2\forall i, \quad \|\mathbf{W}_{[4i:4i+4]}\|_0 = 2

وهذا هو الشكل الوحيد من الندرة الدقيقة الذي يكافئه العتاد فعلًا، ولهذا يهم أكثر من رقم الأصفار البالغ 90% في التقليم غير المنظم. والقيد محلي لا عالمي — فهو لا يهتم بأي اثنين من كل أربعة يبقيان — لذلك فهو تقييد أضعف بكثير من تثبيت قناع عالمي، مع أن 50% هي مستوى الندرة الوحيد المتاح.

from torch.ao.pruning import WeightNormSparsifier

sparsifier = WeightNormSparsifier(
    sparsity_level=0.5,
    sparse_block_shape=(1, 4),
    zeros_per_block=2,
)

sparsifier.prepare(
    model, config=[{"tensor_fqn": "network.0.weight"}]
)
sparsifier.step()
sparsifier.squash_mask()

يتطلب تحقيق التسريع أن يستخدم مسار الاستدلال نوى الموتر المتناثرة — تصدير ONNX مع بناء TensorRT، أو torch.sparse.to_sparse_semi_structured. أما تصدير نموذج مقنّع 2:4 عبر زمن تشغيل كثيف فيمنحك تكلفة الدقة من دون أي فائدة.

النشر في الإنتاج

نموذج استدلال نحيف يدخل قناة عتادية منخفضة الكمون

التحقق

النموذج المقلّم نموذج جديد، لا نموذج قديم مضغوط، ولذلك يمر ببوابة القبول نفسها مثل أي مرشح آخر: إعادة تدريب متحركة وإعادة تحقق خارج العينة وفق تحسين walk-forward، مع تصحيح أثر الاختيار من نسبة Sharpe المنزوعة التضخم. وهذا التصحيح ليس اختياريًا هنا — فـ IMP يولد تسلسلًا من النماذج المرشحة، ولذلك فإن مستوى الندرة الذي يبدو الأفضل عبر عشر جولات اختير تحت بحث، ويجب نزع التضخم عن Sharpe الخاص به بحسب العدد الفعال للتجارب. والقاعدة المسطحة من نوع "ارفض إذا انخفض Sharpe بأكثر من 5%" لا تصمد أمام ذلك الحساب، ولهذا لن تجد واحدة في هذه المقالة.

تكديس التكميم

يتآلف التقليم مع التكميم. نموذج ذو ندرة 90% ومكمم إلى INT8 يملك نسبة ضغط:

CR=1(1s)boriginalbquantized=10.1328=40×\text{CR} = \frac{1}{(1 - s)} \cdot \frac{b_{\text{original}}}{b_{\text{quantized}}} = \frac{1}{0.1} \cdot \frac{32}{8} = 40\times

يتحول نموذج حجمه 48 MB إلى 1.2 MB. هذه دعوى تخزين لا أكثر. أما ما إذا كان نموذج 1.2 MB ينتج القرارات نفسها فهو سؤال منفصل له إجابته الخاصة، وفخ دقة GPU هو سبب طرحه: فقد ثبت في هذه المدونة أن fp32 وحده ينتج خطأ نسبيًا قدره 211 في حساب اختبار رجعي بدا معقولًا تمامًا. وINT8 تخفيض أشد بكثير من ذلك. لا تنشر نموذجًا مكممًا ومقلّمًا إلا خلف بوابة تكافؤ كمية مقابل النموذج الكثيف fp32 — معدل اتفاق القرارات وفارق PnL على فترة محتجزة، لا مجرد ضمان.

المراقبة

قد تكون النماذج المقلّمة أكثر حساسية لتحول التوزيع. يجدر مراقبة ما يلي:

  • ندرة التنشيط: إذا أصدرت العصبونات الباقية أصفارًا في الغالب، فالنموذج الفعال أصغر مما قصدت وربما يتدهور.
  • معايير gradients أثناء إعادة التدريب: تشير gradients المنفجرة إلى أن الشبكة الفرعية الباقية مطالبة بالتعويض بقوة مفرطة عما أزيل.
  • إنتروبيا التنبؤ: يرجح أن يكون نموذج مقلّم يصبح مفرط الثقة على بيانات البنية المجهرية المليئة بالضجيج ملائمًا لنظام التدريب.

الخلاصة

شبكة عصبية مدمجة وكفؤة تتشكل من عدم يقين كثيف

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

والحدس العملي من الأدبيات هو التدريب على نموذج كبير ثم تقليمه بدل تصميم نموذج صغير منذ البداية: فالنموذج الكبير يستكشف سطح الخسارة بفاعلية أكبر، ويحافظ التقليم على المسارات المهمة. وما إذا كان ذلك يصح لنموذج تداول، وعند أي ندرة، وبأي تكلفة دقة، لا يفصلنا عنه سوى مسح IMP واحد — وينبغي قراءة هذه المقالة مرة أخرى بعد ذلك المسح، وبداخلها أرقام.

blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

ابقَ متقدماً على السوق

اشترك في نشرتنا الإخبارية للحصول على رؤى حصرية حول تداول الذكاء الاصطناعي وتحليلات السوق وتحديثات المنصة.

نحترم خصوصيتك. يمكنك إلغاء الاشتراك في أي وقت.