Knowledge Distillation: Compressing Trading Models for Low-Latency Deployment
ความตึงเครียดระหว่างความแม่นยำกับเวลาแฝงในการเทรดที่ขับเคลื่อนด้วย ML มีคำตอบที่เผยแพร่ไว้แล้วในบล็อกนี้ การสร้างแบบจำลองสเปรดด้วยแมชชีนเลิร์นนิง แนะนำ การแบ่งเป็นสองขั้น: โมเดล gradient boosting ที่รวดเร็วทำหน้าที่ส่งคำสั่งแบบเรียลไทม์ซึ่งต้องการเวลาแฝงต่ำ ขณะที่โมเดลเชิงลึกทำงานแบบอะซิงโครนัสและส่งสัญญาณรองหรือปรับพารามิเตอร์ให้มัน สองโมเดล สองจังหวะเวลา ระบบเดียว
การกลั่นความรู้เป็นคำตอบที่ แตกต่าง สำหรับความตึงเครียดเดียวกัน แทนที่จะรันโมเดลช้าควบคู่กับโมเดลเร็ว คุณใช้โมเดลช้าเพียงครั้งเดียวแบบออฟไลน์เพื่อฝึกโมเดลเร็ว — นักเรียนเรียนรู้การแจกแจงความน่าจะเป็นเต็มรูปแบบของครูเหนือผลลัพธ์ ไม่ใช่แค่ป้ายกำกับแบบแข็ง แล้วครูก็ออกจากเส้นทางร้อนโดยสิ้นเชิง เหลือโมเดลเดียวตอนอนุมาน ไม่มีการเชื่อมโยงแบบอะซิงโครนัส และไม่มีช่วงเวลาข้อมูลล้าสมัย
คำตอบไหนดีกว่าเป็นเรื่องเชิงประจักษ์ และบทความนี้ยังไม่ตอบคำถามนั้น สิ่งต่อไปนี้คือกลไก พร้อมคำระบุอย่างชัดเจนถึงการวัดที่จะใช้ตัดสิน ไม่มีผลการทดสอบมาตรฐานอยู่ที่นี่ ตัวเลขที่ปกติควรปรากฏจะถูกแทนด้วยเครื่องหมายระบุว่าต้องรันอะไร
ขอแก้กรอบความคิดตั้งแต่ต้น โดยอ้างอิงจาก DeepLOB และดีปเลิร์นนิงบนสมุดคำสั่งซื้อขาย: ความแม่นยำในการจำแนกสูงไม่ได้แปลเป็นกำไรโดยอัตโนมัติ — การเคลื่อนไหวที่ทำนายต้องมากกว่าสเปรด bid-ask ดังนั้น "รักษาความแม่นยำเชิงทิศทางของครู" จึงไม่ใช่สิ่งที่ควรใช้เป็นเป้าหมายในการปรับระบบกลั่นความรู้
กรอบการทำงานครู-นักเรียน

สูตรดั้งเดิมของ Hinton, Vinyals และ Dean (2015) ตรงไปตรงมา คุณมีโมเดลครู (ใหญ่ ช้า แม่นยำ) และโมเดลนักเรียน (เล็ก เร็ว และกำลังฝึก) นักเรียนเรียนรู้จากสัญญาณสองแบบพร้อมกัน:
- เป้าหมายแบบแข็ง: ป้ายกำกับจริง (เช่น ราคาขึ้นหรือลง)
- เป้าหมายแบบนุ่ม: การแจกแจงความน่าจะเป็นเอาต์พุตของครู เหนือทุกคลาส
ฟังก์ชัน loss ของนักเรียนรวมทั้งสองส่วนเข้าด้วยกัน:
โดย และ คือ logits ของครูและนักเรียนตามลำดับ คือฟังก์ชัน softmax, คือพารามิเตอร์อุณหภูมิ และ ควบคุมสมดุลระหว่างองค์ประกอบของ loss ทั้งสอง
เหตุใดเป้าหมายแบบนุ่มจึงสำคัญต่อการเทรด
การกำหนดราคากลางแบบสามคลาส ขึ้น/ทรงตัว/ลง การตั้ง threshold และเหตุผลที่ความไม่สมดุลที่เกิดขึ้นทำให้ต้องรายงาน weighted F1 แทน accuracy ทั้งหมดนี้ถูกตั้งไว้ใน DeepLOB — ให้ถือว่าใช้ชุดป้ายกำกับเดียวกันที่นี่ ประเด็นเฉพาะของการกลั่นความรู้คือสิ่งที่ครูส่งออกมา ก่อน argmax: คำว่า "ขึ้น" แบบแข็งมีข้อมูลเพียงหนึ่งบิต แต่ 0.72/0.21/0.07 ยังบอกด้วยว่าการเคลื่อนไหวอาจชะลอตัวและแทบจะแน่นอนว่าจะไม่กลับทิศ โครงสร้างระหว่างคลาสนี้คือสัญญาณการฝึกเพิ่มเติม และเป็นเหตุผลที่นักเรียนซึ่งฝึกด้วยเป้าหมายแบบนุ่มอาจ generalize ได้ดีกว่านักเรียนคนเดิมที่ฝึกด้วยป้ายกำกับเพียงอย่างเดียว
ข้อควรระวังว่าสิ่งนี้ไม่ใช่ความมั่นใจแบบใด เอาต์พุต softmax ไม่ใช่ความไม่แน่นอนที่ผ่านการปรับเทียบ และการใช้ 0.55 เทียบกับ 0.85 เป็นอินพุตกำหนดขนาดโพซิชันคือทางลัดที่ conformal prediction สำหรับการเทรด ตั้งใจปฏิเสธ — วิธีนั้นกำหนดขนาดจากความกว้างของช่วง อัตราส่วน edge และตัวกรองไม่เทรดเมื่อช่วงคร่อมศูนย์ ซึ่ง softmax ดิบไม่ได้ให้มา การอ้างว่ากำหนดขนาดได้ที่นี่ต้องวัด calibration ของนักเรียนเทียบกับครู (reliability diagram, ECE) และแสดงว่าการกลั่นความรู้รักษาคุณสมบัตินี้ไว้ ผลลัพธ์นั้นยังไม่มีในบทความนี้
อุณหภูมิและเป้าหมายแบบนุ่ม

พารามิเตอร์อุณหภูมิ ควบคุม "ความนุ่ม" ของการแจกแจงความน่าจะเป็น เมื่อมี logits , softmax ที่ใช้อุณหภูมิคือ:
เมื่อ (softmax มาตรฐาน) การแจกแจงจะแหลม — คลาสเด่นได้รับมวลความน่าจะเป็นส่วนใหญ่ เมื่อ เพิ่มขึ้น การแจกแจงจะแบนลง ทำให้เห็นขนาดสัมพัทธ์ของ logits ได้ชัดเจนขึ้น
| อุณหภูมิ | ผลลัพธ์ | กรณีใช้งาน |
|---|---|---|
| softmax มาตรฐาน แหลม | การอนุมานปกติ | |
| ทำให้นุ่มปานกลาง | การกลั่นความรู้ทั่วไป | |
| ทำให้นุ่มมาก | เมื่อครูมีความมั่นใจสูงมาก | |
| เกือบสม่ำเสมอ | แทบไม่เป็นประโยชน์ ทำให้สัญญาณเจือจาง |
มีเหตุผลที่เป็นไปได้ว่าโมเดลการเทรดควรใช้อุณหภูมิระดับปานกลาง: การพยากรณ์ทางการเงินมีความมั่นใจน้อยกว่าการจำแนกภาพมาก ครูจึงอาจให้ผลลัพธ์ 0.55/0.30/0.15 แทนที่จะเป็น 0.99/0.005/0.005 ทำให้มีความแหลมน้อยลงให้ปรับนุ่มก่อนสัญญาณจะหายไป นี่เป็นเหตุผล ไม่ใช่ข้อค้นพบ — ช่วงค่าต้องมาจากการ sweep บนข้อมูลจริง ให้ให้คะแนนด้วย weighted F1 และอาจต่างกันตาม regime
ตัวคูณ ในพจน์ KL divergence ชดเชยขนาด gradient ที่ลดลงเมื่อใช้อุณหภูมิสูงขึ้น หากไม่มีตัวคูณนี้ loss จากการกลั่นจะเล็กจนแทบไม่มีนัยสำคัญเมื่อ เพิ่มขึ้น
การเลือกอุณหภูมิด้วย grid search
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
การกลั่น ensemble ให้เป็นโมเดลเดียว

ensemble เชิงควอนต์ผสม inductive bias หลายแบบ: gradient-boosted tree บนฟีเจอร์สมุดคำสั่งซื้อขาย, 1D-CNN บน tick ล่าสุด, transformer บนหน้าต่างหลายกรอบเวลา และโมเดลเชิงเส้นบนปัจจัยมหภาค การเฉลี่ยมีเสถียรภาพกว่าสมาชิกใดสมาชิกหนึ่ง และการรันทั้งสี่โมเดลเพิ่มเวลาแฝงกับต้นทุนเป็นทวีคูณ — ซึ่งเป็นสถานการณ์ที่การแบ่งสองขั้นจาก การสร้างแบบจำลองสเปรดด้วยแมชชีนเลิร์นนิง แก้ด้วยการย้ายสมาชิกที่ช้าไปเป็นช่องทางด้านข้างแบบอะซิงโครนัส แต่การกลั่นความรู้รวมทั้งสี่โมเดลให้เป็นนักเรียนเดียวในเส้นทางร้อน
เอาต์พุตของครู ensemble คือค่าเฉลี่ยของเอาต์พุต softmax จากสมาชิก:
โดย คือจำนวนสมาชิกใน ensemble นักเรียนถูกฝึกให้เทียบกับการแจกแจงเฉลี่ยนี้
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)
ความไม่สมมาตรของจำนวนพารามิเตอร์คือประเด็นทั้งหมด: MLP สองชั้นที่มีหน่วยซ่อน 64 หน่วยมีพารามิเตอร์ประมาณ 8,000 ตัวสำหรับงาน 60 ฟีเจอร์ 3 คลาส เทียบกับ ensemble ที่จำนวนรวมสูงถึงหลักล้าน
สิ่งที่นักเรียนเก็บไว้และสิ่งที่สูญเสียไป
นี่คือคำถามเชิงประจักษ์ที่เป็นแกนหลัก และบทความยังไม่ตอบ คำอธิบายตามสัญชาตญาณคือ นักเรียนติดตาม ensemble ได้ในอินพุตที่อยู่ในการแจกแจงเดิม แต่ประสิทธิภาพตกใน regime ที่ตึงเครียด ซึ่งความหลากหลายของ ensemble กำลังทำงาน — แต่ตัวเลข retention จะมีความหมายก็ต่อเมื่อวัดจากข้อมูลสมุดคำสั่งซื้อขายจริง แบ่งตาม regime และรายงานเป็น weighted F1 นักเรียนที่รับมือวันสงบได้แต่ล่มระหว่าง cascade การชำระบัญชีเป็นผลิตภัณฑ์คนละแบบกับนักเรียนที่เสื่อมลงอย่างราบรื่น และตัวเลขรวมไม่สามารถแยกความแตกต่างนี้ได้
มีมาตรการบรรเทาสามอย่างที่ควรทดสอบกับการวัดนั้น แทนที่จะสรุปล่วงหน้า:
- ใส่ช่วงเวลาที่ตึงเครียด ลงในชุดกลั่นความรู้ เพื่อให้นักเรียนเห็น regime ที่คาดว่าช่องว่างจะเปิดกว้าง
- การกลั่นความรู้จากฟีเจอร์ — จับคู่ representation ระดับกลาง ไม่ใช่เฉพาะเอาต์พุตสุดท้าย
- หัว regime เสริม บนนักเรียน บังคับให้ฟีเจอร์ที่รับรู้ regime เข้าไปอยู่ใน trunk ร่วม
Self-Distillation: เมื่อนักเรียนกลายเป็นครู

Self-distillation คือเทคนิคที่โมเดลกลั่นความรู้จากตัวมันเอง
Born-Again Networks (BANs)
ฝึกนักเรียนด้วยสถาปัตยกรรมที่เหมือนกับครู นักเรียนแบบ "เกิดใหม่" มักทำได้ดีกว่าต้นฉบับ และกระบวนการจะทำซ้ำ:
แต่ละรุ่นฝึกด้วยเป้าหมายแบบนุ่มจากรุ่นก่อนหน้า โดยทั่วไปผลลัพธ์จะอิ่มตัวหลังผ่านไปไม่กี่รุ่น สำหรับโมเดลการเทรด วิธีนี้ไม่มีต้นทุนทางสถาปัตยกรรม — ไม่มีฟีเจอร์ใหม่ ไม่มีข้อมูลใหม่ มีเพียงขั้นตอนการฝึกที่ต่างออกไป — จึงทดสอบได้ราคาถูก และไม่มีข้ออ้างที่จะรายงานโดยไม่ทดสอบ
การกลั่นความรู้ด้วยตัวเองตามความลึก
เพิ่มตัวจำแนกเสริมที่เลเยอร์กลาง เอาต์พุตที่ลึกที่สุดทำหน้าที่เป็นครูให้เอาต์พุตที่ตื้นกว่า ตอนอนุมานให้เลือกเอาต์พุตหนึ่งจุด: ตื้นเพื่อเวลาแฝงต่ำ ลึกเพื่อความแม่นยำสูงสุด
แนวคิดนี้เหมาะกับระบบการเทรดมากที่สุด เพราะ ความลึกของเอาต์พุตกลายเป็นปุ่มปรับเวลาแฝงขณะรัน: เครือข่ายเดียวที่ฝึกแล้วครอบคลุมงบประมาณหลายระดับ แทนการผูกมัดกับสถาปัตยกรรมเดียวตอนฝึก เมื่อสมุดคำสั่งซื้อขายเคลื่อนไหวเร็ว ให้เลือกเอาต์พุตตื้นและยอมรับ posterior ที่แย่ลง เมื่อเงียบ ให้จ่ายต้นทุนของความลึกเต็มรูปแบบ ทั้งกราฟความแม่นยำต่อเอาต์พุตและเวลาแฝงต่อเอาต์พุตวัดได้ และจุดตัดของกราฟจะตัดสินว่าปุ่มนี้คุ้มค่าหรือไม่
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
งบประมาณการอนุมานมาจากไหน

การกลั่นความรู้มีความสำคัญก็ต่อเมื่อการอนุมานต้องอยู่ภายใต้งบประมาณที่ตายตัว และบันได tick-to-trade เต็มรูปแบบ — จาก NIC ไป userspace, kernel bypass, ยอดรวมต่ำกว่า 100 µs และระดับต่ำกว่า 10 µs ที่บังคับใช้ FPGA กับ shared memory — มีอธิบายไว้แล้วใน ข้อมูลและการสื่อสารในการเทรดอัลกอริทึม แถวที่บันไดนี้ยังเว้นไว้คือการอนุมานโมเดล และนั่นคือแถวที่การกลั่นความรู้พยายามเติม
อย่าเติมแถวอื่นด้วยตารางเวลาแฝงตามคลาสของโมเดล การสร้างแบบจำลองสเปรดด้วยแมชชีนเลิร์นนิง เผยแพร่การเปรียบเทียบ GBM กับดีปเลิร์นนิงไว้แล้ว พร้อมข้อควรระวังที่สำคัญกว่าตัวเลข: เวลาแฝงขึ้นกับการนำไปใช้ และโมเดล LightGBM เดียวกันใช้เวลาหลายสิบไมโครวินาทีต่อแถวจาก Python แต่ใช้เวลาเพียงไม่กี่ไมโครวินาทีจาก predictor ที่คอมไพล์แล้ว การอ้างเวลาแฝงใด ๆ ที่นี่ต้องระบุ framework, core และ batch size มิฉะนั้นก็เป็นเพียง noise
โดยเฉพาะบน GPU: ต้อง amortize overhead คงที่ต่อการ launch ก่อนที่อุปกรณ์จะช่วยได้ และการอนุมานทีละแถวอยู่ไกลไปทางซ้ายของ roofline ridge ซึ่งมันไม่มีทางไปถึง เมื่อใด GPU จึงคุ้มค่า วัดเส้นโค้ง amortization นี้อย่างถูกต้องด้วยการ sweep batch รวมถึงการ์ด PCIe แบบแยกที่ผลัก ridge ไปทางขวาอีก — อ่านบทความนั้นแทนการเชื่อตัวเลขคงที่ที่จำมาจากที่อื่น
Quantization หลังการกลั่นความรู้
นักเรียนที่ผ่านการกลั่นยังบีบอัดได้อีก: น้ำหนัก INT8 (เร็วขึ้นราว 2 เท่าบน CPU ที่มี AVX-512 VNNI), น้ำหนักแบบไบนารี/เทอร์นารีที่เปลี่ยนการคูณเป็นการบวก และ pruning เพื่อข้ามการคำนวณที่ใกล้ศูนย์
คำกล่าวที่ฟังดูน่าดึงดูดคือ การกลั่นแล้วค่อย quantization รักษาความแม่นยำได้มากกว่า quantization เพียงอย่างเดียว เพราะนักเรียนเรียนรู้ representation ที่กะทัดรัดแล้ว อย่านำไปใช้งานเพียงเพราะคำกล่าวนั้น กับดักความแม่นยำของ GPU คือจุดยืนประจำของบล็อกต่อความแม่นยำเชิงตัวเลขที่ลดลง: มันคืนค่าขยะที่ดูสมเหตุสมผลอย่างเงียบ ๆ และสิ่งที่ทำให้ fast path พร้อมใช้งานคือเกตความเท่าเทียมที่วัดเป็นตัวเลข — fills ที่เปลี่ยนไป, PnL delta ในหน่วย bps — ไม่ใช่คำยืนยัน นักเรียน INT8 เป็นโมเดลคนละตัวจนกว่าจะวัดเกตนี้เทียบกับนักเรียน 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
การนำไปใช้งานบน FPGA: ไปป์ไลน์จากการกลั่นสู่บิตสตรีม

FPGA คือระดับต่ำกว่า 10 µs ใน บันไดเวลาแฝง และ บทวิจารณ์ Tbricks/Broadridge ครอบคลุมการใช้งานจริงควบคู่กับ NIC แบบ kernel bypass — เวลาแฝงคงที่ ไม่มี jitter จากระบบปฏิบัติการ และวางอยู่ใกล้ network stack สิ่งที่บล็อกนี้ยังไม่เคยครอบคลุมคือวิธีนำโมเดลที่กลั่นแล้วลงบน FPGA
บันทึกการใช้งานจริงของ DeepLOB ระบุ ONNX/TensorRT, quantization INT8 และการนำไปใช้งานบน FPGA เป็นสามทางเลือกแล้วหยุดไว้แค่นั้น ส่วนที่สามมีรายละเอียดดังนี้:
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
ข้อจำกัดที่ผูกมัดคือโมเดลต้องพอดีกับองค์ประกอบลอจิกของอุปกรณ์ — LUTs, DSP slices และ block RAM หากคิดเป็นระดับประมาณการ ไม่ใช่การวัด: MLP สองชั้นที่มีหน่วยซ่อน 64 หน่วยและน้ำหนัก INT8 มีการคูณ-สะสมประมาณ 8,000 ครั้งต่อการอนุมาน และมีน้ำหนักราว 16 KB ซึ่งเป็นเพียงส่วนเล็กของชิประดับกลาง นี่คือจุดที่การกลั่นความรู้คุ้มค่า — ครู ensemble ไม่พอดีกับงบประมาณใด ๆ แต่นักเรียนยังห่างจากขีดจำกัดมาก
เครื่องมือที่ทำให้การแปลง PyTorch/ONNX เป็นฮาร์ดแวร์ที่สังเคราะห์ได้เป็นอัตโนมัติ ได้แก่ AMD/Xilinx Vitis AI, hls4ml (จาก CERN) และ FINN (จาก Xilinx Research)
ตัวอย่าง: การแปลง 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() เป็นแหล่งที่น่าเชื่อถือเพียงแห่งเดียวสำหรับตัวเลขทรัพยากรและเวลาแฝงของโมเดล บอร์ด ความแม่นยำ และ reuse factor ที่กำหนด — ตัวเลขเปลี่ยนไปมากแม้เปลี่ยน default_reuse_factor เพียงอย่างเดียว การอ้างตารางการสังเคราะห์ "ทั่วไป" โดยไม่รันจริงคือการเดา
ข้อพิจารณาเชิงปฏิบัติ

การคำนวณ teacher logits ล่วงหน้า
การกลั่นความรู้ต้องใช้การพยากรณ์ของครูบนชุดฝึกทั้งหมด — ต้นทุนออฟไลน์ครั้งเดียวที่คุ้มค่าหากจงใจจ่าย: รัน ensemble หนึ่งครั้ง บันทึก logits และฝึกนักเรียนจากแคช จากนั้นการ sweep อุณหภูมิและการค้นหาสถาปัตยกรรมไม่ต้องเสีย forward pass ของครูเพิ่ม ซึ่งทำให้ sweep ข้างต้นใช้งานได้จริง
มอนิเตอร์เฉพาะสำหรับการกลั่นความรู้หนึ่งเดียว
สุขอนามัยของ pipeline ฟีเจอร์, rolling normalization เพราะพารามิเตอร์ z-score drift, การตรวจสอบ distribution shift ของอินพุต และการฝึกใหม่ตาม regime ล้วนมีอยู่ใน ส่วนการใช้งานจริงของ DeepLOB และใช้ที่นี่ได้เหมือนเดิม
มอนิเตอร์เฉพาะของการกลั่นความรู้คือ KL divergence ระหว่างครูกับนักเรียนบนข้อมูลสด ครูยังคงอยู่แบบออฟไลน์ ให้รันครูบนตัวอย่างอินพุตสดและเปรียบเทียบการแจกแจง KL ที่สูงขึ้นหมายความว่าการประมาณของนักเรียนกำลังเสื่อมใน regime ที่ไม่ได้ใช้กลั่น — และมันจะส่งสัญญาณก่อน accuracy เพราะไม่ต้องรอป้ายกำกับ ต้องปรับเทียบ threshold การฝึกใหม่กับ KL ที่สังเกตได้ในช่วงเวลาที่รู้ว่าดีและเสื่อมแล้ว หากเลือกไว้ล่วงหน้าก็เป็นตัวเลขตามอำเภอใจ
เมื่อใดไม่ควรทำการกลั่นความรู้
- ครูมีขนาดเล็กอยู่แล้ว (โมเดลเชิงเส้น, GBM ตื้น): การกลั่นเพิ่มขั้นตอนใน pipeline โดยไม่ได้บีบอัดอะไร
- เวลาแฝงไม่ใช่ข้อจำกัด (รีบาลานซ์รายวัน, สัญญาณสิ้นวัน): นำครูไปใช้งาน
- ความสามารถในการอธิบายสำคัญกว่าความเร็ว: เครือข่ายที่กลั่นแล้วอธิบายได้ยากกว่า ensemble ต้นไม้ที่มันเข้ามาแทนที่
- การแบ่งสองขั้นทำงานได้แล้ว: หากโมเดลช้าแบบอะซิงโครนัสใน สถาปัตยกรรมการสร้างแบบจำลองสเปรด ให้ผลอยู่ การกลั่นต้องเอาชนะมันในการเปรียบเทียบที่วัดจริงก่อนจึงจะมีเหตุผลพอแทนที่ระบบที่ใช้งานได้
สรุป

การกลั่นความรู้เป็นทางเลือกที่สอดคล้องกันแทนการแบ่งเร็ว/ช้าสองขั้น: ฝึกครูที่ดีที่สุดเท่าที่งบประมาณออฟไลน์เอื้อ ถ่ายทอดโครงสร้างเป้าหมายแบบนุ่มไปยังนักเรียนที่เล็กพอสำหรับเส้นทางร้อน ทำ quantization และนำไปใช้บน CPU หรือ FPGA รูปแบบตามความลึกไปไกลกว่านั้น โดยทำให้เวลาแฝงเป็นตัวเลือกขณะรันแทนที่จะเป็นตัวเลือกตอนฝึก
สิ่งที่บทความนี้จงใจไม่อ้างคือมันดีกว่าสิ่งที่บล็อกเผยแพร่อยู่แล้ว คำตัดสินต้องใช้การวัดสามอย่างบนข้อมูลสมุดคำสั่งซื้อขายจริง: เส้นโค้ง retention ของ weighted F1 ระหว่างนักเรียนกับ ensemble แยกตาม regime, การ sweep อุณหภูมิ และเกตความเท่าเทียม INT8 ตามแนวทางของ กับดักความแม่นยำของ GPU จนกว่าจะมีผลลัพธ์เหล่านี้ นี่คือคำอธิบายเทคนิค ไม่ใช่คำแนะนำให้นำไปใช้งาน
เอกสารอ้างอิง
-
Hinton, G., Vinyals, O., & Dean, J. (2015). การกลั่นความรู้ในโครงข่ายประสาทเทียม. arXiv:1503.02531
-
Furlanello, T., Lipton, Z. C., Tschannen, M., Itti, L., & Anandkumar, A. (2018). Born-Again Neural Networks. ICML. arXiv:1805.04770
-
Zhang, L., Song, J., Gao, A., Chen, J., Bao, C., & Ma, K. (2019). Be Your Own Teacher: Improve the Performance of Convolutional Neural Networks via Self Distillation. ICCV. arXiv:1905.08094
-
Romero, A., Ballas, N., Kahou, S. E., Chassang, A., Gatta, C., & Bengio, Y. (2015). FitNets: Hints for Thin Deep Nets. ICLR. arXiv:1412.6550
-
Gou, J., Yu, B., Maybank, S. J., & Tao, D. (2021). การกลั่นความรู้: บทสำรวจ. International Journal of Computer Vision, 129, 1789-1819. arXiv:2006.05525
-
Duarte, J., et al. (2018). การอนุมานโครงข่ายประสาทเชิงลึกอย่างรวดเร็วบน FPGA สำหรับฟิสิกส์อนุภาค (hls4ml). Journal of Instrumentation, 13, P07027. arXiv:1804.06913
-
Umuroglu, Y., et al. (2017). FINN: เฟรมเวิร์กสำหรับการอนุมานเครือข่ายประสาทไบนารีที่รวดเร็วและปรับขนาดได้. FPGA '17. arXiv:1612.07119
-
Zhang, Z., Zohren, S., & Roberts, S. (2019). DeepLOB: โครงข่ายประสาทคอนโวลูชันเชิงลึกสำหรับสมุดคำสั่งซื้อขายแบบจำกัด. IEEE Transactions on Signal Processing, 67(11), 3001-3012. arXiv:1808.03668
ผู้เขียน
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.