← Retour aux articles
August 14, 2026
5 min de lecture

Model Pruning for Low-Latency Trading Inference

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

Notre article DeepLOB termine sa section de déploiement avec trois puces - ONNX plus TensorRT, quantification INT8, FPGA - et aucun traitement d'entre eux. Cet article est le traitement manquant du premier problème sous les trois : le modèle est plus grand qu’il ne devrait l’être. L'élagage du réseau neuronal supprime les paramètres redondants, et l'affirmation intéressante dans la littérature n'est pas que cela économise de la mémoire mais qu'un sous-réseau contenant 10 à 20 % des poids peut égaler la précision du modèle dense.

Le blog a déjà fait valoir que la latence est importante - ZigBolt sur le chemin de messagerie et la taxe IPC avec l'arithmétique du seuil de rentabilité - et spread modeling possède déjà le un compromis rapide mais légèrement pire contre un compromis lent mais meilleur, complété par un tableau d'amélioration du gradient par rapport à l'apprentissage en profondeur qui comporte une ligne de latence d'inférence. Ce qu'aucun d'eux ne couvre, c'est comment vous réduisez la taille d'un modèle donné. Parmi les étapes d’une boucle de cotation, l’inférence de modèle est celle entièrement sous notre contrôle ; les segments de transport sont couverts, avec des numéros p50/p95/p99 reproductibles, dans communication de données pour algotrading.

Qu'est-ce que cet article : le code mathématique et de travail pour l'élagage de magnitude, l'élagage structuré, l'élagage de magnitude itératif, l'élagage de mouvement, la distillation des connaissances et la parcimonie semi-structurée NVIDIA 2:4, appliqués à un MLP de trading.

Ce que ce n'est pas : un résultat mesuré. Chaque article empirique de ce blog contient une ligne de provenance ou un dépôt compagnon, et celui-ci ne contient encore aucun des deux. La courbe rareté/précision/latence est présentée ci-dessous à titre d'expérience à exécuter, et non comme un tableau à citer. Traitez tout ici comme la méthode et les chiffres comme en attente.

Ce que la taille vous achète

Réseau neuronal compact s'intégrant dans une couche de cache rapide

La contrainte est de taille. Considérons un modèle moyenne fréquence : un MLP à 4 couches avec 2 048 unités cachées sur les fonctionnalités du carnet de commandes :

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

Pour din=100d_{\text{in}} = 100, h=2048h = 2048, L=4L = 4, dout=3d_{\text{out}} = 3, cela représente environ 12,6 millions de paramètres, soit environ 48 Mo en float32. L2 fait généralement entre 1 et 4 Mo, les poids ne correspondent donc pas ; ils sont diffusés de plus loin à chaque passe avant. Élaguez-en 95 % et vous obtenez environ 630 000 paramètres effectifs et 2,4 Mo, ce qui convient.

Que cela se traduise en temps d'horloge murale dépend du fait que le noyau soit limité à la mémoire, et c'est une question d'intensité arithmétique plutôt qu'une question de taille. L'échelle de régime moteur backtest utilise le modèle de ligne de toit (Williams, Waterman & Patterson) à travers un exemple mesuré plutôt que d'affirmer un facteur de pénalité ; le même cadrage s'applique ici, et la même discipline devrait : mesurer les octets déplacés avant de réclamer l'accélération.

Principes fondamentaux de la taille

Élagage structuré et non structuré sur un réseau neuronal

Taille non structurée

L’approche la plus simple : fixer les poids individuels à zéro en fonction de leur ampleur. Étant donné une matrice de poids WRm×n\mathbf{W} \in \mathbb{R}^{m \times n}, créez un masque binaire M{0,1}m×n\mathbf{M} \in \{0, 1\}^{m \times n} tel que :

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 est un seuil choisi pour atteindre le niveau de parcimonie souhaité ss:

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

La matrice élaguée est W=WM\mathbf{W}' = \mathbf{W} \odot \mathbf{M}, avec \odot le produit Hadamard. L’intuition est que les poids proches de zéro contribuent peu au résultat de la couche.

Le problème, énoncé clairement car il est facile de mal interpréter le numéro de parcimonie : une parcimonie non structurée ne se traduit pas par une accélération du matériel standard. Une matrice avec 90 % de zéros émet toujours le même nombre de multiplications-accumulations, sauf si vous passez à des noyaux clairsemés ou à du matériel prenant en charge la parcimonie. Lorsque le code ci-dessous s'imprime Sparsity: 90.0%, c'est un nombre de zéros - ce n'est pas un 10x, et sur un processeur GEMM dense, ce n'est pas non plus un 1,01x. Les chemins qui permettent de gagner du temps sont l'élagage structuré (matrices plus petites) et la parcimonie semi-structurée 2:4 (support matériel), tous deux ci-dessous.

Taille structurée

L'élagage structuré supprime des neurones, des canaux ou des têtes d'attention entières. Pour une couche linéaire y=Wx+b\mathbf{y} = \mathbf{W}\mathbf{x} + \mathbf{b} avec WRm×n\mathbf{W} \in \mathbb{R}^{m \times n}, en supprimant le neurone jj remet à zéro le jj-ème rangée de W\mathbf{W} et le jj-ème élément de b\mathbf{b}:

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

Neurones avec le plus petit 2\ell_2-norme, passe en premier. Il s'agit de la variante qui produit des matrices véritablement plus petites, mais seulement si vous reconstruisez réellement les couches. La remise à zéro des lignes et le maintien du tenseur dans sa forme d'origine ne changent rien au nombre de FLOP ; l'étape de reconstruction dans la section d'implémentation est ce qui convertit le masque en un 512×n512 \times n matrice.

Pour les couches convolutives, l’analogue est l’élagage du filtre. Donné WRCout×Cin×k×k\mathbf{W} \in \mathbb{R}^{C_{\text{out}} \times C_{\text{in}} \times k \times k}, l'importance du filtre de sortie ii est:

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}

Supprimer le filtre ii élimine un canal de sortie entier, réduisant ainsi les FLOP proportionnellement.

L'hypothèse des billets de loterie

Petit sous-réseau gagnant isolé au sein d'un vaste réseau neuronal

En 2019, Frankle et Carbin ont introduit l'hypothèse du billet de loterie (LTH) : au sein d'un réseau dense initialisé de manière aléatoire, il existe un sous-réseau clairsemé — un « ticket gagnant » — qui, formé à partir de son initialisation d'origine, correspond à la précision du réseau complet dans un nombre comparable d'itérations.

Formellement, considérons f(x;θ0)f(\mathbf{x}; \boldsymbol{\theta}_0) initialisé avec θ0Dθ\boldsymbol{\theta}_0 \sim \mathcal{D}_\theta. Après entraînement à la convergence on obtient θ\boldsymbol{\theta}^* et en dériver un masque de taille m\mathbf{m}. Le LTH déclare qu'il existe m\mathbf{m} tel que :

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}^*)

avec m0θ0\|\mathbf{m}\|_0 \ll |\boldsymbol{\theta}_0|. Les expériences originales portaient sur MNIST et CIFAR-10, où les tickets gagnants conservaient 10 à 20 % des paramètres. Rien à ce sujet n'est transféré aux données du carnet de commandes par hypothèse - les caractéristiques LOB sont non stationnaires et l'étiquette est presque du bruit, ce qui est un régime différent de la classification d'images exactement de la manière qui pourrait avoir de l'importance.

Élagage itératif de magnitude (IMP)

Le ticket est trouvé par IMP :

  1. Initialisez le réseau avec θ0\boldsymbol{\theta}_0.
  2. S'entraîner à la convergence, obtenir θ\boldsymbol{\theta}^*.
  3. Taillez le p%p\% de poids avec la plus petite magnitude, créant un masque m\mathbf{m}.
  4. Réinitialisez les poids survivants à leurs valeurs dans θ0\boldsymbol{\theta}_0 (rembobinage).
  5. Répétez à partir de l'étape 2 avec le réseau masqué.

Chaque fraction de pruneaux ronde pp (généralement 20%), donc après nn tours (1p)n(1-p)^n des paramètres survivent. Après 10 tours à p=0.2p = 0.2, il en reste environ 10,7 %.

Trois hypothèses sur les modèles de trading, aucune d'entre elles n'a été testée

Il est tentant de prétendre que LTH devrait fonctionner particulièrement bien sur les données de marché. Trois de ces arguments apparaissent ; tous les trois sont des hypothèses, et les présenter comme des faits est le mode d’échec que ce blog vise à éviter.

  1. Les signaux financiers sont rares. La majeure partie d'un instantané du carnet de commandes est constituée de bruit, de sorte qu'un sous-réseau clairsemé peut être naturellement aligné sur un signal clairsemé. Testable : comparez IMP à un masque aléatoire de même rareté ; si la parcimonie elle-même fait le travail, le masque aléatoire ne devrait pas être loin derrière.
  2. Les tickets gagnants se généralisent à tous les régimes. Celle-ci est une affirmation empirique sur les marchés sans aucune citation derrière elle, et c'est la plus intéressante des trois. Il est directement testable par rapport aux étiquettes de régime de détection de régime avec HMM : trouvez le ticket dans le régime A, recyclez-le dans le régime B et comparez-le avec un ticket trouvé nativement dans B.
  3. La parcimonie se régularise. Une capacité effective inférieure peut réduire l'ajustement au bruit de la microstructure - ce qui se manifesterait par le fait que l'écart hors échantillon du modèle élagué est plus petit que celui du modèle dense, et pas seulement comparable.

Implémentation : élagage d'un MLP de trading

Couches neuronales transformées grâce à un masque clairsemé précis

Le modèle de base

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():,}")

Taille de magnitude non structurée

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%}")

Taille structurée, avec la reconstruction qui la rend réelle

Le masquage des lignes représente la moitié du travail. La moitié qui produit l'accélération reconstruit chaque couche dans sa forme réduite, ce qui signifie propager la suppression vers l'avant : supprimer la ligne jj de couche ii supprime également la colonne jj de couche i+1i+1 et canal jj de n'importe quel BatchNorm1d entre eux.

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

Deux choses à vérifier avant de faire confiance à cela, dans le même esprit que les portes d'équivalence du reste du blog :

  • Formes. rebuilt devrait afficher les dimensions cachées à (1fraction)h(1 - \text{fraction}) \cdot h — 512 pour fraction=0.75, h=2048h = 2048 - et un nombre de paramètres qui a diminué quadratiquement, puisque les deux dimensions des matrices intérieures rétrécissent.
  • Sorties. Entrée eval() mode, rebuilt(x) doit correspondre à celui du modèle masqué rebuilt-sortie gratuite avec tolérance à virgule flottante sur le même lot. Si ce n'est pas le cas, la propagation des colonnes est erronée et chaque numéro en aval mesure un modèle différent de celui que vous pensez.

Le test de survie des lignes suppose qu'une ligne masquée est exactement nulle et qu'une ligne active ne l'est pas. Cela vaut pour ln_structured sortir; cela ne tiendrait pas si une autre procédure produisait un neurone vivant véritablement nul, alors affirmez le décompte des survivants par rapport à la fraction demandée plutôt que de faire confiance aveuglément au test de norme.

Élagage itératif de l'ampleur (recherche de billets de loterie)

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 est la matière première de la courbe rareté/précision que cet article vous doit. eval_fn doit être véritablement hors échantillon, sur des divisions purgées - une analyse IMP notée dans l'échantillon rapportera une belle courbe qui ne signifie rien.

Le mesurer

Frontière d'efficacité abstraite équilibrant la précision de la rareté et la latence

La latence est mesurée avec la même convention de harnais que le reste du blog — échauffement exclu, best-of-N, p50/p95/p99 rapporté plutôt qu'une moyenne — et ce protocole, avec le code, se trouve dans Polars vs pandas. Trois points spécifiques à la taille :

  • Comparez le modèle reconstruit, pas celui masqué. Un modèle masqué de taille de lot 1 mesure la forme dense.
  • Signaler la taille du lot. Le lot 1 (boucle de citation) et le lot 256 (balayage de recherche) se trouvent de différents côtés de la ligne liée à la mémoire/liée au calcul, et l'élagage les aide différemment.
  • Précision du rapport sur la même répartition, au même horizon, avec la définition de l'étiquette indiquée. Un tableau de latence sans la colonne de précision de correspondance est un argument en faveur de la suppression complète du modèle.

Techniques avancées

Chemins de compression réseau avancés convergeant vers un modèle efficace

Taille avec distillation des connaissances

Plutôt que d’élaguer et d’affiner de manière isolée, utilisez le modèle dense original en tant qu’enseignant. L'élève élagué minimise une combinaison de perte de tâches et de divergence KL par rapport à la distribution des résultats de l'enseignant :

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 et zs\mathbf{z}_s sont les logits des enseignants et des élèves, TT est la température, et α\alpha équilibre les objectifs. Le T2T^2 Le facteur redimensionne les gradients de distillation, qui autrement diminuent à mesure que 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

Taille de mouvement

Plutôt que d’élaguer en grandeur absolue, l’élagage par mouvement (Sanh et al., 2020) élague les poids qui se rapprochent de zéro pendant l’entraînement. Le score d'importance accumule le produit gradient-poids :

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)

Les pondérations avec des scores négatifs sont supprimées. L'argument en faveur de l'élagage de l'amplitude concerne spécifiquement le réglage fin : lorsque vous adaptez un modèle pré-entraîné, la distribution de l'amplitude a été façonnée par la tâche de pré-entraînement, donc l'amplitude est un signal d'importance obsolète et la direction du déplacement est un signal plus récent. Pour un modèle de trading recyclé sur des fenêtres glissantes, c'est la situation la plus courante que la formation à partir de zéro.

NVIDIA 2:4 : parcimonie structurée

Les GPU NVIDIA Ampere et versions ultérieures prennent en charge une parcimonie structurée 2: 4 dans le matériel : sur 4 poids contigus, exactement 2 doivent être nuls.

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

Il s’agit de la seule forme de parcimonie fine que le matériel récompense réellement, c’est pourquoi elle est plus importante que le nombre de 90 % de zéros provenant de l’élagage non structuré. La contrainte est locale plutôt que globale - peu importe * lesquels * deux sur quatre survivent - il s'agit donc d'une restriction beaucoup plus faible que la fixation d'un masque global, bien que 50 % soit le seul niveau de parcimonie proposé.

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()

Pour réaliser l'accélération, le chemin d'inférence doit utiliser les cœurs de tenseurs clairsemés - une exportation ONNX plus une construction TensorRT, ou torch.sparse.to_sparse_semi_structured. L'exportation d'un modèle masqué 2:4 via un environnement d'exécution dense vous offre le coût de la précision mais aucun avantage.

Déploiement en production

Modèle d'inférence Lean entrant dans un conduit matériel à faible latence

Validation

Un modèle élagué est un nouveau modèle, pas un ancien compressé, et il passe par la même porte d'acceptation que tout autre candidat : recyclage progressif et revalidation hors échantillon par optimisation walk-forward, avec la correction de l'effet de sélection de le ratio de Sharpe dégonflé. Cette correction n'est pas facultative ici - IMP génère une séquence de modèles candidats, de sorte que le niveau de parcimonie qui semble le meilleur sur dix tours a été choisi lors de la recherche, et son Sharpe doit être dégonflé par le nombre effectif d'essais. Une règle plate comme « rejeter si Sharpe chute de plus de 5 % » ne survit pas à cette arithmétique, c'est pourquoi vous n'en trouverez pas dans cet article.

Empilement de quantification

L'élagage compose avec la quantification. Un modèle clairsemé à 90 % et quantifié en INT8 a un taux de compression de :

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

Un modèle de 48 Mo devient 1,2 Mo. Il s'agit d'une réclamation de stockage et rien de plus. Que le modèle de 1,2 Mo produise les mêmes décisions est une question distincte avec sa propre réponse, et le piège de précision du GPU est la raison de le poser : sur ce blog, il a été démontré que fp32 seul produisait une erreur relative de 211 dans un calcul de backtest qui semblait tout à fait raisonnable. INT8 est une réduction bien plus agressive que cela. Expédiez un modèle quantifié et élagué uniquement derrière une porte de parité quantifiée par rapport au modèle dense fp32 - taux d'accord de décision et delta PnL sur une période de conservation, pas une assurance.

Surveillance

Les modèles élagués peuvent être plus sensibles au changement de distribution. A voir :

  • Préparité d'activation : si les neurones survivants émettent principalement des zéros, le modèle effectif est plus petit que prévu et probablement dégradant.
  • Normes de gradient pendant le recyclage : l'explosion des gradients suggère que le sous-réseau survivant est invité à compenser de manière trop agressive ce qui a été supprimé.
  • Entropie de prédiction : un modèle élagué qui devient trop confiant sur les données de microstructure bruyantes est probablement adapté au régime d'entraînement.

Conclusion

Réseau neuronal compact et efficace résolvant une incertitude dense

Les méthodes sont bien établies et, jusqu’à ce que le balayage soit terminé, c’est tout ce que prétend cet article. La taille non structurée vous donne un nombre de parcimonie et aucune vitesse. L'élagage structuré vous donne de la vitesse si – et seulement si – vous reconstruisez les couches plutôt que de les masquer. L'hypothèse des billets de loterie suggère que le modèle compact existe déjà à l'intérieur du modèle surparamétré, bien que cela ait été démontré sur des références d'images et non sur des données de carnet de commandes, et les trois raisons pour lesquelles il « devrait » fonctionner sur les données de marché indiquées ci-dessus sont des hypothèses auxquelles sont attachées des expériences, et non des résultats.

L’heuristique pratique issue de la littérature consiste à former grand et à élaguer plutôt que de concevoir petit dès le départ : le grand modèle explore plus efficacement le paysage des pertes et l’élagage préserve les voies qui comptent. Que cela soit vrai pour un modèle commercial, à quelle rareté et à quel coût de précision, est un balayage IMP - et cet article devrait être relu après ce balayage, avec des chiffres.

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

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.