← 기사 목록으로
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 세금](/en/blog/post/ipc-tax-backtest-engine)] 및 확산 모델링는 이미 빠르지만 약간 나쁜 대 느리지만 더 나은 절충점을 소유하고 있으며 그래디언트 부스팅 대 딥 러닝 테이블이 완비되어 있습니다. 추론 지연 행이 있습니다. 그 중 어느 것도 다루지 않는 것은 주어진 모델을 더 작게 만드는 방법입니다. 인용 루프의 단계 중 모델 추론은 우리가 완전히 제어할 수 있는 단계입니다. 전송 다리는 algotrading를 위한 데이터 통신]에서 재현 가능한 p50/p95/p99 번호로 덮여 있습니다.

이 문서의 내용: 거래 MLP에 적용되는 크기 가지치기, 구조화된 가지치기, 반복 크기 가지치기, 이동 가지치기, 지식 증류 및 NVIDIA 2:4 반구조적 희소성에 대한 수학 및 작업 코드입니다.

아님: 측정된 결과입니다. 이 블로그의 모든 경험적 기사에는 출처 라인이나 동반 저장소가 포함되어 있지만, 이 기사는 아직 둘 다 포함되어 있지 않습니다. 희소성 대 정확도 대 대기 시간 곡선은 인용할 표가 아닌 실행할 실험으로 아래에 설명되어 있습니다. 여기에 있는 모든 것을 메서드로 처리하고 숫자를 보류 중인 것으로 처리합니다.

모델 가지치기로 얻는 것

빠른 캐시 레이어에 맞는 컴팩트 신경망

제약 조건은 크기 1입니다. 주문서 기능에 대해 2048개의 숨겨진 유닛이 있는 4층 MLP인 중간 주파수 모델을 고려해보세요.

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의 경우 대략 1,260만 개의 매개변수(float32에서는 약 48MB)입니다. L2는 일반적으로 1-4MB이므로 가중치가 맞지 않습니다. 모든 정방향 패스에서 더 먼 곳에서 스트리밍됩니다. 그 중 95%를 정리하면 대략 630K의 유효 매개변수와 2.4MB가 됩니다.

이것이 벽시계 시간으로 변환되는지 여부는 커널이 메모리에 바인딩되어 있는지 여부에 따라 달라지며 이는 크기 문제가 아닌 산술 강도 문제입니다. 백테스트 엔진 속도 래더는 패널티 요소를 주장하기보다는 측정된 예를 통해 루프라인 모델(Williams, Waterman & Patterson)을 작동합니다. 여기에는 동일한 프레이밍이 적용되며 동일한 규율이 ​​적용되어야 합니다. 속도 향상을 주장하기 전에 이동된 바이트를 측정해야 합니다.

가지치기 기초

신경 격자를 통한 구조화 및 비구조화 가지치기

구조화되지 않은 가지치기

가장 간단한 접근 방식은 크기에 따라 개별 가중치를 0으로 설정하는 것입니다. 가중치 행렬 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는 Hadamard 제품입니다. 직관상 0에 가까운 가중치는 레이어의 출력에 거의 영향을 미치지 않습니다.

희소성 숫자를 잘못 읽기 쉽기 때문에 문제가 명확하게 설명됩니다. 구조화되지 않은 희소성은 표준 하드웨어의 속도 향상으로 해석되지 않습니다. 90%가 0인 행렬은 희소성을 지원하는 하드웨어나 희소 커널로 전환하지 않는 한 여전히 동일한 수의 곱셈 누적을 발행합니다. 아래 코드가 Sparsity: 90.0%를 인쇄하면 이는 0의 개수입니다. 이는 10배도 아니며 밀도가 높은 CPU GEMM에서도 1.01x도 아닙니다. 시간을 벌 수 있는 경로는 구조화된 가지치기(더 작은 행렬)와 2:4 반구조화된 희소성(하드웨어 지원)입니다. 둘 다 아래에 나와 있습니다.

구조화된 가지치기

구조화된 가지치기는 전체 뉴런, 채널 또는 주의 헤드를 제거합니다. WRm×n\mathbf{W} \in \mathbb{R}^{m \times n}가 있는 선형 계층 y=Wx+b\mathbf{y} = \mathbf{W}\mathbf{x} + \mathbf{b}의 경우 뉴런 jj를 제거하면 W\mathbf{W}jj번째 행과 b\mathbf{b}jj번째 요소가 0이 됩니다.

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

가장 작은 2\ell_2-norm을 가진 뉴런이 먼저 옵니다. 이는 실제로 더 작은 행렬을 생성하는 변형이지만 실제로 레이어를 다시 빌드하는 경우에만 가능합니다. 행을 0으로 만들고 텐서를 원래 모양으로 남겨두면 FLOP 수에 아무런 변화가 없습니다. 구현 섹션의 재구축 단계는 마스크를 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를 제거하면 전체 출력 채널이 제거되어 FLOP가 비례적으로 감소합니다.

복권 가설

광대한 신경 격자 내에 격리된 작은 승리 하위 네트워크

2019년 Frankle과 Carbin은 LTH(복권 가설)을 도입했습니다. 즉, 무작위로 초기화된 조밀한 네트워크 내에 희소 하위 네트워크("당첨 티켓")가 존재합니다. 이 하위 네트워크는 원래 초기화에서 훈련되어 비슷한 횟수의 반복에서 전체 네트워크의 정확도와 일치합니다.

공식적으로는 θ0Dθ\boldsymbol{\theta}_0 \sim \mathcal{D}_\theta로 초기화된 f(x;θ0)f(\mathbf{x}; \boldsymbol{\theta}_0)를 고려하세요. 수렴에 대한 훈련 후 θ\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가 유지됩니다. p=0.2p = 0.2에서 10라운드 후에 약 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%}")

구조화된 가지치기, 이를 현실로 만드는 재구축

행을 마스킹하는 것은 작업의 절반입니다. 속도 향상을 생성하는 절반은 각 레이어를 축소된 모양으로 다시 작성하는 것입니다. 이는 제거를 앞으로 전파하는 것을 의미합니다. ii 레이어의 jj 행을 삭제하면 i+1i+1 레이어의 jj 열과 그 사이에 있는 BatchNorm1d의 채널 jj도 삭제됩니다.

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(fraction=0.75, h=2048h = 2048의 경우 512)에 숨겨진 차원과 내부 행렬의 두 차원이 모두 축소되므로 2차적으로 감소한 매개변수 개수를 표시해야 합니다.
  • 출력. eval() 모드에서 rebuilt(x)는 마스크된 모델의 rebuilt 없는 출력을 동일한 배치의 부동 소수점 허용 오차와 일치해야 합니다. 그렇지 않다면 열 전파가 잘못된 것이며 모든 다운스트림 숫자가 생각하는 것과 다른 모델을 측정하고 있는 것입니다.

행 생존 테스트에서는 마스크된 행이 정확히 0이고 활성 행이 그렇지 않다고 가정합니다. 이는 ln_structured 출력에도 적용됩니다. 다른 절차에서 실제로 모두 0인 살아있는 뉴런을 생성하는 경우에는 유지되지 않으므로 표준 테스트를 맹목적으로 신뢰하기보다는 요청된 부분에 대해 생존자 수를 주장하십시오.

반복적 크기 가지치기 (복권 검색)

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 vs pandas. 가지치기와 관련된 세 가지 사항:

  • 마스크된 모델이 아닌 재구축된 모델을 벤치마킹합니다. 배치 크기 1의 마스크 모델은 조밀한 형태를 측정합니다.
  • 배치 크기를 보고합니다. 배치 1(인용 루프)과 배치 256(연구 스윕)은 메모리 바인딩/계산 바인딩 라인의 서로 다른 측면에 있으며 가지치기는 서로 다른 방식으로 도움이 됩니다.
  • 명시된 라벨 정의를 사용하여 동일한 분할, 동일한 수평선에서 정확도를 보고합니다. 일치하는 정확도 열이 없는 지연 시간 테이블은 모델을 완전히 삭제하기 위한 인수입니다.

고급 기술

하나의 효율적인 모델로 통합되는 고급 네트워크 압축 경로

지식 증류를 통한 가지치기

단독으로 가지치기 및 미세 조정을 수행하는 대신 원래의 밀집 모델을 교사로 사용합니다. 가지치기된 학생은 교사의 출력 분포에서 과제 손실과 KL 발산의 조합을 최소화합니다.

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}_tzs\mathbf{z}_s는 교사 및 학생 로짓이고, TT는 온도이고, α\alpha는 목표의 균형을 유지합니다. T2T^2 인자는 증류 기울기의 크기를 다시 조정하며, 그렇지 않으면 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 et al., 2020)는 훈련 중에 0을 향해 움직이는 가중치를 가지치기합니다. 중요도 점수는 경사 가중치 곱을 누적합니다.

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)

음수 점수가 있는 가중치는 제거됩니다. 크기 가지치기에 대한 주장은 특히 미세 조정에 관한 것입니다. 사전 훈련된 모델을 적용할 때 크기 분포는 사전 훈련 작업에 의해 형성되므로 크기는 오래된 중요도 신호이고 이동 방향은 더 새로운 신호입니다. 롤링 윈도우에서 재교육된 거래 모델의 경우 이는 처음부터 교육하는 것보다 더 일반적인 상황입니다.

NVIDIA 2:4 구조적 희소성

암페어 이상 NVIDIA GPU는 하드웨어에서 2:4 구조적 희소성을 지원합니다. 즉, 인접한 4개의 가중치 중 정확히 2개는 0이어야 합니다.

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

이는 하드웨어가 실제로 보상하는 세분화된 희소성의 한 형태이므로 구조화되지 않은 가지치기에서 90%-0 숫자보다 더 중요한 이유입니다. 제약 조건은 글로벌이 아니라 로컬입니다. 4개 중 2개가 살아남는 것은 상관하지 않습니다. 따라서 제공되는 유일한 희소성 수준은 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 최적화에 따른 롤링 재훈련 및 샘플 외부 재검증]입니다. 여기서는 수정이 선택 사항이 아닙니다. IMP는 후보 모델의 시퀀스를 생성하므로 10라운드에 걸쳐 가장 잘 보이는 희소성 수준이 검색에서 선택되었으며 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

48MB 모델은 1.2MB가 됩니다. 이는 저장 공간 청구일 뿐이며 그 이상은 아닙니다. 1.2MB 모델이 동일한 결정을 내리는지 여부는 자체 답변이 있는 별도의 질문이며 GPU 정밀도 Trap가 이를 묻는 이유입니다. 이 블로그에서는 완전히 합리적으로 보이는 백테스트 계산에서 fp32만으로도 상대 오류 211을 생성하는 것으로 나타났습니다. INT8은 그보다 훨씬 더 공격적인 감소입니다. fp32 밀집 모델에 대해 정량화된 패리티 게이트 뒤에만 양자화 및 정리된 모델을 제공합니다. 즉, 보증이 아닌 보류 기간에 대한 의사결정 동의율 및 PnL 델타입니다.

모니터링

가지치기 모델은 분포 변화에 더 민감할 수 있습니다. 시청할 가치가 있는 것:

  • 활성화 희소성: 살아남은 뉴런이 대부분 0을 방출하는 경우 효과적인 모델은 의도한 것보다 작고 성능이 저하될 수 있습니다.
  • 재훈련 중 기울기 표준: 폭발적인 기울기는 살아남은 하위 네트워크가 제거된 것에 대해 너무 공격적으로 보상하도록 요청받고 있음을 나타냅니다.
  • 예측 엔트로피: 시끄러운 미세 구조 데이터에 대해 과신하게 되는 정리된 모델이 훈련 체제에 적합할 가능성이 높습니다.

결론

촘촘한 불확실성을 해결하는 컴팩트하고 효율적인 신경망

방법은 잘 확립되어 있으며, 청소가 실행될 때까지 이것이 이 기사에서 주장하는 전부입니다. 구조화되지 않은 가지치기는 희소성 수치를 제공하고 속도는 제공하지 않습니다. 구조화된 가지치기는 레이어를 마스크하는 대신 레이어를 다시 구성하는 경우에만 속도를 제공합니다. 복권 가설은 콤팩트 모델이 과모수화된 모델 내부에 이미 존재함을 시사하지만 이는 주문장 데이터가 아닌 이미지 벤치마크에서 입증되었으며 위에 제시된 시장 데이터에서 작동해야 하는 세 가지 이유는 발견이 아니라 첨부된 실험을 통한 가설입니다.

문헌의 실용적인 경험적 방법은 처음부터 작게 설계하는 것보다 크게 훈련하고 정리하는 것입니다. 대규모 모델은 손실 환경을 보다 효과적으로 탐색하고 가지치기는 중요한 경로를 보존합니다. 이것이 거래 모델에 적용되는지 여부, 희소성, 정확도 비용은 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

시장에서 앞서 나가세요

뉴스레터를 구독하여 독점적인 AI 트레이딩 통찰력, 시장 분석 및 플랫폼 업데이트를 받아보세요.

귀하의 개인정보를 존중합니다. 언제든지 구독을 취소할 수 있습니다.