Knowledge Distillation: Compressing Trading Models for Low-Latency Deployment
A tensão entre precisão e latência na negociação orientada por ML já tem uma resposta publicada neste blog. Modelagem de propagação com aprendizado de máquina recomenda uma divisão em dois estágios: um modelo rápido de aumento de gradiente faz a cotação em tempo real crítica para latência, enquanto um modelo profundo é executado de forma assíncrona e alimenta-o com um sinal secundário ou ajusta seus parâmetros. Dois modelos, dois relógios, um sistema.
A destilação do conhecimento é uma resposta diferente para a mesma tensão. Em vez de executar o modelo lento junto com o rápido, você o usa uma vez, off-line, para treinar o rápido – o aluno aprende a distribuição completa de probabilidades do professor sobre os resultados, e não apenas os rótulos rígidos, e o professor então abandona completamente o caminho quente. Um modelo no momento da inferência, sem acoplamento assíncrono, sem janela de obsolescência.
A resposta vencedora é empírica e este artigo ainda não a responde. O que se segue é o maquinário, além de uma declaração explícita das medidas que o decidiriam. Nada aqui é um resultado de referência; onde normalmente iria um número, há um marcador dizendo o que deve ser executado.
Uma correção de enquadramento inicial, de DeepLOB e aprendizado profundo na carteira de pedidos: alta precisão de classificação não se traduz automaticamente em lucro – o movimento previsto deve limpar o spread de compra e venda. "Preservar a precisão direcional do professor" é, portanto, a coisa errada para otimizar uma configuração de destilação.
A estrutura professor-aluno

A formulação original de Hinton, Vinyals e Dean (2015) é direta. Você tem um modelo de professor (grande, lento, preciso) e um modelo de estudante (pequeno, rápido, para ser treinado). O aluno aprende com dois sinais simultaneamente:
- Alvos difíceis: os rótulos de verdade (por exemplo, o preço subiu ou desceu)
- Metas flexíveis: a distribuição de probabilidade de produção do professor em todas as aulas
A função de perda do aluno combina ambos:
onde e são os logits do professor e do aluno, é a função softmax, é o parâmetro de temperatura, e controla o equilíbrio entre os dois componentes de perda.
Por que as metas flexíveis são importantes para a negociação
A formulação de três classes de preço médio ascendente/estacionário/descendente, o limiar e por que o desequilíbrio resultante significa que você relata F1 ponderado em vez de precisão estão todos configurados em DeepLOB - assuma esse esquema de rótulo aqui. O ponto específico da destilação é o que o professor emite antes do argmax: um hard "up" carrega um bit, enquanto 0,72/0,21/0,07 também diz que o movimento pode parar e quase certamente não será revertido. Essa estrutura entre classes é o sinal extra de treinamento, e é por isso que um aluno com alvo leve pode generalizar melhor do que o mesmo aluno treinado apenas com rótulos.
Um aviso sobre o que essa confiança não é. A saída do Softmax não é uma incerteza calibrada, e tratar 0,55 vs. 0,85 como uma entrada de dimensionamento de posição é o atalho que previsão conforme para negociação existe para recusar - deriva o dimensionamento da largura do intervalo, uma proporção de borda e um filtro sem negociação quando o intervalo ultrapassa zero, nenhum dos quais um softmax bruto oferece a você. Ganhar a reivindicação de dimensionamento aqui significa medir a calibração do aluno em relação à do professor (diagrama de confiabilidade, ECE) e mostrar que a destilação a preserva. Esse resultado ainda não está neste artigo.
Temperatura e alvos flexíveis

O parâmetro de temperatura controla a "suavidade" da distribuição de probabilidade. Dados dados , o softmax com temperatura é:
Quando (softmax padrão), a distribuição é pontiaguda – a classe dominante obtém a maior parte da massa de probabilidade. Como aumenta, a distribuição se achata, revelando mais claramente as magnitudes relativas dos logits.
| Temperatura | Efeito | Caso de uso |
|---|---|---|
| Softmax padrão, pico | Inferência normal | |
| Suavização moderada | Destilação geral | |
| Suavização pesada | Quando o professor está muito confiante | |
| Quase uniforme | Raramente útil, apaga o sinal |
Há um argumento plausível de que os modelos de negociação desejam uma temperatura moderada: as previsões financeiras são muito menos confiáveis do que a classificação de imagens, portanto, um professor pode produzir 0,55/0,30/0,15 em vez de 0,99/0,005/0,005, deixando menos pico para suavizar antes que o sinal desapareça. Isto é um argumento, não uma conclusão – o intervalo tem de provir de uma varredura em dados reais, pontuados por F1 ponderado, e pode diferir consoante o regime.
O O fator no termo de divergência KL compensa as magnitudes reduzidas do gradiente em temperaturas mais altas. Sem ele, a perda de destilação tornar-se-ia insignificantemente pequena, uma vez que aumenta.
Escolhendo a temperatura por meio da pesquisa em grade
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from sklearn.metrics import f1_score
def distillation_loss(
student_logits: torch.Tensor,
teacher_logits: torch.Tensor,
labels: torch.Tensor,
temperature: float,
alpha: float,
) -> torch.Tensor:
"""Combined hard-target + soft-target distillation loss."""
hard_loss = F.cross_entropy(student_logits, labels)
soft_teacher = F.log_softmax(teacher_logits / temperature, dim=-1)
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
soft_loss = F.kl_div(
soft_student,
soft_teacher,
log_target=True,
reduction="batchmean",
)
return alpha * hard_loss + (1.0 - alpha) * (temperature ** 2) * soft_loss
def search_temperature(
teacher: nn.Module,
student_factory, # callable returning a fresh student
train_loader: DataLoader,
val_loader: DataLoader,
temperatures: list[float] = [1, 2, 3, 5, 8, 12],
alpha: float = 0.3,
epochs: int = 30,
lr: float = 1e-3,
device: str = "cuda",
):
"""Grid search over temperature, scored by weighted F1 (not accuracy:
the up/flat/down label scheme is heavily imbalanced toward flat)."""
best_f1, best_T, best_student = 0.0, 1.0, None
for T in temperatures:
student = student_factory().to(device)
optimizer = torch.optim.AdamW(student.parameters(), lr=lr)
for epoch in range(epochs):
student.train()
for X, y in train_loader:
X, y = X.to(device), y.to(device)
with torch.no_grad():
teacher_logits = teacher(X)
student_logits = student(X)
loss = distillation_loss(
student_logits, teacher_logits, y, T, alpha
)
optimizer.zero_grad()
loss.backward()
optimizer.step()
student.eval()
preds, targets = [], []
with torch.no_grad():
for X, y in val_loader:
preds.append(student(X.to(device)).argmax(dim=-1).cpu())
targets.append(y)
f1 = f1_score(
torch.cat(targets), torch.cat(preds), average="weighted"
)
print(f"T={T:>4.1f} val_weighted_f1={f1:.4f}")
if f1 > best_f1:
best_f1, best_T, best_student = f1, T, student
print(f"\nBest temperature: T={best_T}, val_weighted_f1={best_f1:.4f}")
return best_T, best_student
Destilando conjuntos em um único modelo

Um conjunto quântico mistura vieses indutivos: uma árvore intensificada por gradiente em recursos de carteira de pedidos, um 1D-CNN sobre ticks recentes, um transformador em janelas de vários períodos de tempo, um modelo linear em fatores macro. A média é mais estável do que qualquer membro sozinho, e a execução de todos os quatro multiplica a latência e o custo - a situação que a divisão em dois estágios da modelagem de propagação com aprendizado de máquina lida ao rebaixar membros lentos para um canal lateral assíncrono. Em vez disso, a destilação colapsa todos os quatro em um único aluno no caminho quente.
A produção do professor do conjunto é a média das saídas softmax de seus membros:
onde é o número de membros do conjunto. O aluno é treinado contra essa distribuição média.
class EnsembleTeacher(nn.Module):
"""Wraps K models, returns averaged logits for distillation."""
def __init__(self, models: list[nn.Module]):
super().__init__()
self.models = nn.ModuleList(models)
@torch.no_grad()
def forward(self, x: torch.Tensor) -> torch.Tensor:
logits = torch.stack([m(x) for m in self.models], dim=0)
return logits.mean(dim=0) # average logits, not softmax
class TradingStudent(nn.Module):
"""Lightweight MLP for sub-millisecond inference."""
def __init__(self, input_dim: int, hidden: int = 64, n_classes: int = 3):
super().__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, hidden),
nn.ReLU(),
nn.BatchNorm1d(hidden),
nn.Linear(hidden, hidden),
nn.ReLU(),
nn.BatchNorm1d(hidden),
nn.Linear(hidden, n_classes),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.net(x)
A assimetria de contagem de parâmetros é o ponto principal: um MLP de duas camadas com 64 unidades ocultas tem cerca de 8.000 parâmetros para uma tarefa de 60 recursos e 3 classes, contra um conjunto cuja contagem combinada chega a milhões.
O que o aluno retém e o que perde
Esta é a questão empírica fundamental e o artigo não a responde. A intuição é que o aluno acompanha o conjunto na distribuição e cai em regimes de tensão, onde a diversidade do conjunto está a fazer o trabalho - mas um valor de retenção significa apenas algo medido em dados reais da carteira de encomendas, dividido por regime, e reportado como ponderado F1. Um aluno que se mantém firme em dias calmos e desmaia durante uma cascata de liquidação é um produto diferente daquele que se degrada graciosamente, e um número agregado não consegue diferenciá-los.
Vale a pena testar três mitigações em relação a essa medição, em vez de afirmar antecipadamente:
- Inclua períodos de estresse no conjunto de destilação, para que o aluno veja os regimes onde se espera que a lacuna se abra.
- Destilação baseada em recursos — combine representações intermediárias, não apenas resultados finais.
- Regime auxiliar no aluno, forçando recursos com reconhecimento de regime no tronco compartilhado.
Autodestilação: quando o aluno se torna professor

A autodestilação é uma técnica em que um modelo destila o conhecimento de si mesmo.
Redes Nascidas de Novo (BANs)
Treine um aluno com arquitetura idêntica à do professor. O aluno “nascido de novo” geralmente supera o original e o processo repete:
Cada geração treina com base em alvos fáceis da anterior, com ganhos tipicamente saturados após algumas gerações. Para modelos de negociação, isso não custa nada em termos de arquitetura — sem novos recursos, sem novos dados, apenas um procedimento de treinamento diferente — o que também significa que é barato testar e não há desculpa para relatá-lo como não testado.
Autodestilação em profundidade
Anexe classificadores auxiliares em camadas intermediárias. A saída mais profunda serve de professora para as mais rasas. Na inferência você escolhe uma saída: superficial para menor latência, profunda para máxima precisão.
Esta é a ideia que melhor se adapta a um sistema de negociação, porque a profundidade de saída se torna um botão de latência do tempo de execução: uma rede treinada cobre uma variedade de orçamentos em vez de se comprometer com uma única arquitetura no momento do treinamento. Quando o livro está se movendo rápido você pega a saída rasa e aceita uma posterior pior; quando está quieto você paga pela profundidade total. Ambas as curvas de precisão por saída e latência por saída são mensuráveis, e seu cruzamento decide se vale a pena ter o botão.
class SelfDistillingNet(nn.Module):
"""Network with early-exit classifiers for variable-latency inference."""
def __init__(self, input_dim: int, n_classes: int = 3):
super().__init__()
self.block1 = nn.Sequential(
nn.Linear(input_dim, 128), nn.ReLU(), nn.BatchNorm1d(128)
)
self.block2 = nn.Sequential(
nn.Linear(128, 64), nn.ReLU(), nn.BatchNorm1d(64)
)
self.block3 = nn.Sequential(
nn.Linear(64, 32), nn.ReLU(), nn.BatchNorm1d(32)
)
self.exit1 = nn.Linear(128, n_classes)
self.exit2 = nn.Linear(64, n_classes)
self.exit3 = nn.Linear(32, n_classes) # final exit
def forward(
self, x: torch.Tensor, exit_layer: int = 3
) -> torch.Tensor:
h1 = self.block1(x)
if exit_layer == 1:
return self.exit1(h1)
h2 = self.block2(h1)
if exit_layer == 2:
return self.exit2(h2)
h3 = self.block3(h2)
return self.exit3(h3)
def forward_all_exits(self, x: torch.Tensor):
"""Return logits from all exits (for self-distillation training)."""
h1 = self.block1(x)
h2 = self.block2(h1)
h3 = self.block3(h2)
return self.exit1(h1), self.exit2(h2), self.exit3(h3)
def self_distillation_step(
model: SelfDistillingNet,
x: torch.Tensor,
y: torch.Tensor,
temperature: float = 4.0,
alpha: float = 0.5,
) -> torch.Tensor:
"""One training step with self-distillation from deepest exit."""
logits_1, logits_2, logits_3 = model.forward_all_exits(x)
loss_hard = F.cross_entropy(logits_3, y)
loss_distill_1 = distillation_loss(
logits_1, logits_3.detach(), y, temperature, alpha
)
loss_distill_2 = distillation_loss(
logits_2, logits_3.detach(), y, temperature, alpha
)
return loss_hard + 0.5 * loss_distill_1 + 0.5 * loss_distill_2
De onde vem o orçamento de inferência

A destilação só importa se a inferência tiver que ficar dentro de um orçamento rígido, e a escada completa do tick-to-trade - NIC-to-userspace, desvio do kernel, o total abaixo de 100 µs e o nível sub-10 µs que força o FPGA e a memória compartilhada - já está definido em dados e comunicação na negociação algorítmica. A linha que a escada deixa aberta é a inferência do modelo, e é a linha que a destilação está tentando preencher.
Resista ao preenchimento das outras linhas com uma tabela de latência de classe de modelo. Modelagem de propagação com aprendizado de máquina já publica a comparação GBM vs-deep-learning mais a ressalva que importa mais do que os números: a latência depende da implementação, e o mesmo modelo LightGBM leva dezenas de microssegundos por linha do Python, mas alguns microssegundos de um preditor compilado. Qualquer reivindicação de latência aqui deve nomear a estrutura, o núcleo e o tamanho do lote, ou será ruído.
Especificamente em GPUs: a sobrecarga fixa por inicialização deve ser amortizada antes que um dispositivo ajude, e a inferência de linha única fica bem à esquerda da linha do telhado, onde nunca está. Quando a GPU compensa mede essa curva de amortização adequadamente com uma varredura em lote, incluindo como uma placa PCIe discreta empurra a crista mais para a direita - leia isso em vez de confiar em uma constante citada na memória.
Quantização após destilação
Um aluno destilado comprime ainda mais: pesos INT8 (aproximadamente 2x na CPU com AVX-512 VNNI), pesos binários/ternários que transformam multiplicações em adições e poda para pular computação próxima de zero.
A afirmação tentadora é que a destilação seguida de quantização preserva mais precisão do que apenas a quantização, uma vez que o aluno já aprendeu uma representação compacta. Não envie nele. A armadilha de precisão da GPU é a posição do blog sobre precisão numérica reduzida: ele silenciosamente retornou lixo de aparência plausível, e o que tornou o caminho rápido distribuível foi uma porta de equivalência quantificada - preenchimentos deslocados, delta PnL em bps - não uma afirmação. Um aluno INT8 é um modelo diferente até que esse portão seja medido em relação ao aluno FP32.
import torch.quantization as quant
def quantize_student(student: nn.Module, calibration_loader: DataLoader):
"""Post-training static quantization for CPU deployment."""
student.cpu()
student.eval()
student.qconfig = quant.get_default_qconfig("x86")
student_prepared = quant.prepare(student)
with torch.no_grad():
for X, _ in calibration_loader:
student_prepared(X)
student_quantized = quant.convert(student_prepared)
return student_quantized
Implantação de FPGA: o pipeline de destilação para bitstream

FPGAs são a camada abaixo de 10 µs na escada de latência e a revisão de Tbricks/Broadridge cobre-os na produção junto com NICs de bypass de kernel - latência determinística, sem jitter do sistema operacional, co-localizado com a pilha de rede. O que não é abordado em nenhum lugar deste blog é como um modelo destilado chega a um.
Notas de produção do DeepLOB lista ONNX/TensorRT, quantização INT8 e implantação de FPGA como três opções e para por aí. É nisso que o terceiro se expande:
1. Train ensemble teacher (offline, GPU cluster, hours/days)
|
2. Distill to small MLP student (offline, single GPU, minutes)
|
3. Quantize student to INT8 / fixed-point (offline, CPU)
|
4. Convert to HLS (High-Level Synthesis) or RTL
|
5. Synthesize FPGA bitstream (offline, hours)
|
6. Deploy to FPGA card in production server
|
7. Inference: market data -> FPGA -> trading signal
A restrição de ligação é que o modelo deve caber nos elementos lógicos do dispositivo – LUTs, fatias DSP, bloco de RAM. Como um orçamento de ordem de grandeza, em vez de uma medida: um MLP de 2 camadas com 64 unidades ocultas e pesos INT8 é da ordem de 8.000 acumulações multiplicadas por inferência e ~16 KB de pesos, uma pequena fração de uma parte intermediária. É aqui que a destilação ganha seu sustento - o professor do conjunto não cabe em nenhum orçamento; o aluno não está nem perto do limite.
As ferramentas que automatizam PyTorch/ONNX para hardware sintetizável incluem AMD/Xilinx Vitis AI, hls4ml (do CERN) e FINN (da Xilinx Research).
Exemplo: Conversão hls4ml
import hls4ml
import onnx
dummy_input = torch.randn(1, 60) # 60 input features
torch.onnx.export(student, dummy_input, "student.onnx", opset_version=13)
hls_config = hls4ml.utils.config_from_onnx_model(
onnx.load("student.onnx"),
granularity="name",
default_precision="ap_fixed<16,8>",
default_reuse_factor=1, # full parallelism
)
hls_model = hls4ml.converters.convert_from_onnx_model(
"student.onnx",
hls_config=hls_config,
output_dir="hls_student",
backend="VivadoAccelerator",
board="alveo-u250",
)
hls_model.compile()
hls_model.build(csim=True, synth=True)
hls_model.report()
hls_model.report() é a única fonte confiável de números de recursos e latência para um determinado modelo, placa, precisão e fator de reutilização - os números mudam substancialmente com default_reuse_factor sozinho. Citar uma tabela de síntese "típica" sem executá-la é adivinhação.
Considerações Práticas

Pré-computando Logits do Professor
A destilação precisa de previsões do professor sobre todo o conjunto de treinamento – um custo off-line único que vale a pena pagar deliberadamente: execute o conjunto uma vez, persista os logits, treine os alunos contra o cache. Varreduras de temperatura e pesquisas de arquitetura não custam nada a mais nas passagens do professor, o que torna as varreduras acima práticas.
O único monitor específico para destilação
Higiene do pipeline de recursos, normalização contínua devido ao desvio dos parâmetros de pontuação z, monitoramento de mudança de distribuição de entrada e retreinamento acionado por regime são todos abordados na seção de produção do DeepLOB e aplicam-se inalterados aqui.
O monitor específico para destilação é divergência KL professor-aluno em dados ao vivo. O professor ainda existe offline; execute-o em uma amostra de entradas ao vivo e compare as distribuições. O aumento do KL significa que a aproximação do aluno é degradante em regimes em que não foi destilado – e dispara antes da precisão, porque não espera por rótulos. O limite de reciclagem deve ser calibrado em relação ao KL observado em períodos sabidamente bons e sabidamente degradados; escolhido a priori é arbitrário.
Quando não destilar
- O professor já é pequeno (um modelo linear, um GBM raso): a destilação adiciona um estágio de pipeline para não haver compressão.
- A latência não é uma restrição (rebalanceamento diário, sinais de fim do dia): implante o professor.
- A interpretabilidade supera a velocidade: uma rede destilada é mais difícil de explicar do que o conjunto de árvores que ela substituiu.
- A divisão em dois estágios já funciona: se o modelo lento assíncrono na arquitetura de modelagem de propagação estiver funcionando, a destilação terá que superá-lo em uma comparação medida antes de justificar a substituição de um sistema em funcionamento.
Resumo

A destilação é uma alternativa coerente à divisão rápida/lenta de dois estágios: treinar o melhor professor que você puder pagar off-line, transferir sua estrutura de alvo flexível para um aluno pequeno o suficiente para o caminho quente, quantizar, implantar em CPU ou FPGA. A variante em profundidade vai além e torna a latência uma escolha de tempo de execução, em vez de uma escolha de tempo de treinamento.
O que este artigo deliberadamente não afirma é que nada disso supera o que o blog já publica. Esse veredicto precisa de três medições em dados reais da carteira de pedidos: a curva de retenção F1 ponderada entre aluno e conjunto dividida por regime, a varredura de temperatura e uma porta de paridade INT8 no estilo da armadilha de precisão da GPU. Até que existam, esta é uma descrição de uma técnica, não uma recomendação para implementá-la.
Referências
-
Hinton, G., Vinyals, O., & Dean, J. (2015). Destilando o Conhecimento em uma Rede Neural. arXiv:1503.02531
-
Furlanello, T., Lipton, ZC, Tschannen, M., Itti, L., & Anandkumar, A. (2018). Redes Neurais Nascidas de Novo. ICML. arXiv:1805.04770
-
Zhang, L., Song, J., Gao, A., Chen, J., Bao, C., & Ma, K. (2019). Seja seu próprio professor: melhore o desempenho de redes neurais convolucionais por meio de autodestilação. ICCV. arXiv:1905.08094
-
Romero, A., Ballas, N., Kahou, SE, Chassang, A., Gatta, C., & Bengio, Y. (2015). FitNets: dicas para redes finas e profundas. ICLR. arXiv:1412.6550
-
Gou, J., Yu, B., Maybank, SJ e Tao, D. (2021). Destilação de Conhecimento: Uma Pesquisa. Jornal Internacional de Visão Computacional, 129, 1789-1819. arXiv:2006.05525
6.Duarte, J., et al. (2018). Inferência rápida de redes neurais profundas em FPGAs para física de partículas (hls4ml). Jornal de Instrumentação, 13, P07027. arXiv:1804.06913
-
Umuroglu, Y., et al. (2017). FINN: Uma Estrutura para Inferência de Rede Neural Binarizada Rápida e Escalável. FPGA '17. arXiv:1612.07119
-
Zhang, Z., Zohren, S. e Roberts, S. (2019). DeepLOB: Redes Neurais Convolucionais Profundas para Livros de Pedidos Limitados. Transações IEEE sobre processamento de sinais, 67(11), 3001-3012. arXiv:1808.03668
Authors
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.