← กลับไปยังบทความ
August 11, 2026
อ่าน 5 นาที

Knowledge Distillation: Compressing Trading Models for Low-Latency Deployment

Knowledge Distillation: Compressing Trading Models for Low-Latency Deployment
#model-compression
#distillation
#latency
#HFT
#deployment

ความตึงเครียดระหว่างความแม่นยำกับเวลาแฝงในการเทรดที่ขับเคลื่อนด้วย ML มีคำตอบที่เผยแพร่ไว้แล้วในบล็อกนี้ การสร้างแบบจำลองสเปรดด้วยแมชชีนเลิร์นนิง แนะนำ การแบ่งเป็นสองขั้น: โมเดล gradient boosting ที่รวดเร็วทำหน้าที่ส่งคำสั่งแบบเรียลไทม์ซึ่งต้องการเวลาแฝงต่ำ ขณะที่โมเดลเชิงลึกทำงานแบบอะซิงโครนัสและส่งสัญญาณรองหรือปรับพารามิเตอร์ให้มัน สองโมเดล สองจังหวะเวลา ระบบเดียว

การกลั่นความรู้เป็นคำตอบที่ แตกต่าง สำหรับความตึงเครียดเดียวกัน แทนที่จะรันโมเดลช้าควบคู่กับโมเดลเร็ว คุณใช้โมเดลช้าเพียงครั้งเดียวแบบออฟไลน์เพื่อฝึกโมเดลเร็ว — นักเรียนเรียนรู้การแจกแจงความน่าจะเป็นเต็มรูปแบบของครูเหนือผลลัพธ์ ไม่ใช่แค่ป้ายกำกับแบบแข็ง แล้วครูก็ออกจากเส้นทางร้อนโดยสิ้นเชิง เหลือโมเดลเดียวตอนอนุมาน ไม่มีการเชื่อมโยงแบบอะซิงโครนัส และไม่มีช่วงเวลาข้อมูลล้าสมัย

คำตอบไหนดีกว่าเป็นเรื่องเชิงประจักษ์ และบทความนี้ยังไม่ตอบคำถามนั้น สิ่งต่อไปนี้คือกลไก พร้อมคำระบุอย่างชัดเจนถึงการวัดที่จะใช้ตัดสิน ไม่มีผลการทดสอบมาตรฐานอยู่ที่นี่ ตัวเลขที่ปกติควรปรากฏจะถูกแทนด้วยเครื่องหมายระบุว่าต้องรันอะไร

ขอแก้กรอบความคิดตั้งแต่ต้น โดยอ้างอิงจาก DeepLOB และดีปเลิร์นนิงบนสมุดคำสั่งซื้อขาย: ความแม่นยำในการจำแนกสูงไม่ได้แปลเป็นกำไรโดยอัตโนมัติ — การเคลื่อนไหวที่ทำนายต้องมากกว่าสเปรด bid-ask ดังนั้น "รักษาความแม่นยำเชิงทิศทางของครู" จึงไม่ใช่สิ่งที่ควรใช้เป็นเป้าหมายในการปรับระบบกลั่นความรู้

กรอบการทำงานครู-นักเรียน

โมเดลครูขนาดใหญ่ถ่ายโอนความเข้าใจไปยังนักเรียนขนาดกะทัดรัด

สูตรดั้งเดิมของ Hinton, Vinyals และ Dean (2015) ตรงไปตรงมา คุณมีโมเดลครู TT (ใหญ่ ช้า แม่นยำ) และโมเดลนักเรียน SS (เล็ก เร็ว และกำลังฝึก) นักเรียนเรียนรู้จากสัญญาณสองแบบพร้อมกัน:

  1. เป้าหมายแบบแข็ง: ป้ายกำกับจริง yy (เช่น ราคาขึ้นหรือลง)
  2. เป้าหมายแบบนุ่ม: การแจกแจงความน่าจะเป็นเอาต์พุตของครู qTq_T เหนือทุกคลาส

ฟังก์ชัน loss ของนักเรียนรวมทั้งสองส่วนเข้าด้วยกัน:

L=αLCE(y,σ(zS))+(1α)T2DKL(σ(zTT)σ(zST))\mathcal{L} = \alpha \cdot \mathcal{L}_{\text{CE}}(y, \sigma(z_S)) + (1 - \alpha) \cdot T^2 \cdot D_{\text{KL}}\left(\sigma\left(\frac{z_T}{T}\right) \| \sigma\left(\frac{z_S}{T}\right)\right)

โดย zTz_T และ zSz_S คือ logits ของครูและนักเรียนตามลำดับ σ\sigma คือฟังก์ชัน softmax, TT คือพารามิเตอร์อุณหภูมิ และ α\alpha ควบคุมสมดุลระหว่างองค์ประกอบของ loss ทั้งสอง

เหตุใดเป้าหมายแบบนุ่มจึงสำคัญต่อการเทรด

การกำหนดราคากลางแบบสามคลาส ขึ้น/ทรงตัว/ลง การตั้ง threshold ±α\pm\alpha และเหตุผลที่ความไม่สมดุลที่เกิดขึ้นทำให้ต้องรายงาน 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) และแสดงว่าการกลั่นความรู้รักษาคุณสมบัตินี้ไว้ ผลลัพธ์นั้นยังไม่มีในบทความนี้

อุณหภูมิและเป้าหมายแบบนุ่ม

การทำให้เป้าหมายความน่าจะเป็นของเครือข่ายประสาทนุ่มลง

พารามิเตอร์อุณหภูมิ TT ควบคุม "ความนุ่ม" ของการแจกแจงความน่าจะเป็น เมื่อมี logits ziz_i, softmax ที่ใช้อุณหภูมิคือ:

σ(zi;T)=exp(zi/T)jexp(zj/T)\sigma(z_i; T) = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}

เมื่อ T=1T = 1 (softmax มาตรฐาน) การแจกแจงจะแหลม — คลาสเด่นได้รับมวลความน่าจะเป็นส่วนใหญ่ เมื่อ TT เพิ่มขึ้น การแจกแจงจะแบนลง ทำให้เห็นขนาดสัมพัทธ์ของ logits ได้ชัดเจนขึ้น

อุณหภูมิ ผลลัพธ์ กรณีใช้งาน
T=1T = 1 softmax มาตรฐาน แหลม การอนุมานปกติ
T=25T = 2\text{--}5 ทำให้นุ่มปานกลาง การกลั่นความรู้ทั่วไป
T=510T = 5\text{--}10 ทำให้นุ่มมาก เมื่อครูมีความมั่นใจสูงมาก
T>20T > 20 เกือบสม่ำเสมอ แทบไม่เป็นประโยชน์ ทำให้สัญญาณเจือจาง

มีเหตุผลที่เป็นไปได้ว่าโมเดลการเทรดควรใช้อุณหภูมิระดับปานกลาง: การพยากรณ์ทางการเงินมีความมั่นใจน้อยกว่าการจำแนกภาพมาก ครูจึงอาจให้ผลลัพธ์ 0.55/0.30/0.15 แทนที่จะเป็น 0.99/0.005/0.005 ทำให้มีความแหลมน้อยลงให้ปรับนุ่มก่อนสัญญาณจะหายไป นี่เป็นเหตุผล ไม่ใช่ข้อค้นพบ — ช่วงค่าต้องมาจากการ sweep บนข้อมูลจริง ให้ให้คะแนนด้วย weighted F1 และอาจต่างกันตาม regime

ตัวคูณ T2T^2 ในพจน์ KL divergence ชดเชยขนาด gradient ที่ลดลงเมื่อใช้อุณหภูมิสูงขึ้น หากไม่มีตัวคูณนี้ loss จากการกลั่นจะเล็กจนแทบไม่มีนัยสำคัญเมื่อ TT เพิ่มขึ้น

การเลือกอุณหภูมิด้วย 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 ของหลายโมเดลที่บรรจบสู่แกนกลางเดียว

ensemble เชิงควอนต์ผสม inductive bias หลายแบบ: gradient-boosted tree บนฟีเจอร์สมุดคำสั่งซื้อขาย, 1D-CNN บน tick ล่าสุด, transformer บนหน้าต่างหลายกรอบเวลา และโมเดลเชิงเส้นบนปัจจัยมหภาค การเฉลี่ยมีเสถียรภาพกว่าสมาชิกใดสมาชิกหนึ่ง และการรันทั้งสี่โมเดลเพิ่มเวลาแฝงกับต้นทุนเป็นทวีคูณ — ซึ่งเป็นสถานการณ์ที่การแบ่งสองขั้นจาก การสร้างแบบจำลองสเปรดด้วยแมชชีนเลิร์นนิง แก้ด้วยการย้ายสมาชิกที่ช้าไปเป็นช่องทางด้านข้างแบบอะซิงโครนัส แต่การกลั่นความรู้รวมทั้งสี่โมเดลให้เป็นนักเรียนเดียวในเส้นทางร้อน

เอาต์พุตของครู ensemble คือค่าเฉลี่ยของเอาต์พุต softmax จากสมาชิก:

qensemble(x)=1Kk=1Kσ(zk(x)/T)q_{\text{ensemble}}(x) = \frac{1}{K} \sum_{k=1}^{K} \sigma(z_k(x) / T)

โดย KK คือจำนวนสมาชิกใน 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 การชำระบัญชีเป็นผลิตภัณฑ์คนละแบบกับนักเรียนที่เสื่อมลงอย่างราบรื่น และตัวเลขรวมไม่สามารถแยกความแตกต่างนี้ได้

มีมาตรการบรรเทาสามอย่างที่ควรทดสอบกับการวัดนั้น แทนที่จะสรุปล่วงหน้า:

  1. ใส่ช่วงเวลาที่ตึงเครียด ลงในชุดกลั่นความรู้ เพื่อให้นักเรียนเห็น regime ที่คาดว่าช่องว่างจะเปิดกว้าง
  2. การกลั่นความรู้จากฟีเจอร์ — จับคู่ representation ระดับกลาง ไม่ใช่เฉพาะเอาต์พุตสุดท้าย
  3. หัว regime เสริม บนนักเรียน บังคับให้ฟีเจอร์ที่รับรู้ regime เข้าไปอยู่ใน trunk ร่วม

Self-Distillation: เมื่อนักเรียนกลายเป็นครู

โมเดลที่ปรับปรุง representation ของตัวเอง

Self-distillation คือเทคนิคที่โมเดลกลั่นความรู้จากตัวมันเอง

Born-Again Networks (BANs)

ฝึกนักเรียนด้วยสถาปัตยกรรมที่เหมือนกับครู นักเรียนแบบ "เกิดใหม่" มักทำได้ดีกว่าต้นฉบับ และกระบวนการจะทำซ้ำ:

M0distillM1distillM2distillM_0 \xrightarrow{\text{distill}} M_1 \xrightarrow{\text{distill}} M_2 \xrightarrow{\text{distill}} \cdots

แต่ละรุ่นฝึกด้วยเป้าหมายแบบนุ่มจากรุ่นก่อนหน้า โดยทั่วไปผลลัพธ์จะอิ่มตัวหลังผ่านไปไม่กี่รุ่น สำหรับโมเดลการเทรด วิธีนี้ไม่มีต้นทุนทางสถาปัตยกรรม — ไม่มีฟีเจอร์ใหม่ ไม่มีข้อมูลใหม่ มีเพียงขั้นตอนการฝึกที่ต่างออกไป — จึงทดสอบได้ราคาถูก และไม่มีข้ออ้างที่จะรายงานโดยไม่ทดสอบ

การกลั่นความรู้ด้วยตัวเองตามความลึก

เพิ่มตัวจำแนกเสริมที่เลเยอร์กลาง เอาต์พุตที่ลึกที่สุดทำหน้าที่เป็นครูให้เอาต์พุตที่ตื้นกว่า ตอนอนุมานให้เลือกเอาต์พุตหนึ่งจุด: ตื้นเพื่อเวลาแฝงต่ำ ลึกเพื่อความแม่นยำสูงสุด

แนวคิดนี้เหมาะกับระบบการเทรดมากที่สุด เพราะ ความลึกของเอาต์พุตกลายเป็นปุ่มปรับเวลาแฝงขณะรัน: เครือข่ายเดียวที่ฝึกแล้วครอบคลุมงบประมาณหลายระดับ แทนการผูกมัดกับสถาปัตยกรรมเดียวตอนฝึก เมื่อสมุดคำสั่งซื้อขายเคลื่อนไหวเร็ว ให้เลือกเอาต์พุตตื้นและยอมรับ 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

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 จนกว่าจะมีผลลัพธ์เหล่านี้ นี่คือคำอธิบายเทคนิค ไม่ใช่คำแนะนำให้นำไปใช้งาน

เอกสารอ้างอิง

  1. Hinton, G., Vinyals, O., & Dean, J. (2015). การกลั่นความรู้ในโครงข่ายประสาทเทียม. arXiv:1503.02531

  2. Furlanello, T., Lipton, Z. C., Tschannen, M., Itti, L., & Anandkumar, A. (2018). Born-Again Neural Networks. ICML. arXiv:1805.04770

  3. 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

  4. Romero, A., Ballas, N., Kahou, S. E., Chassang, A., Gatta, C., & Bengio, Y. (2015). FitNets: Hints for Thin Deep Nets. ICLR. arXiv:1412.6550

  5. Gou, J., Yu, B., Maybank, S. J., & Tao, D. (2021). การกลั่นความรู้: บทสำรวจ. International Journal of Computer Vision, 129, 1789-1819. arXiv:2006.05525

  6. Duarte, J., et al. (2018). การอนุมานโครงข่ายประสาทเชิงลึกอย่างรวดเร็วบน FPGA สำหรับฟิสิกส์อนุภาค (hls4ml). Journal of Instrumentation, 13, P07027. arXiv:1804.06913

  7. Umuroglu, Y., et al. (2017). FINN: เฟรมเวิร์กสำหรับการอนุมานเครือข่ายประสาทไบนารีที่รวดเร็วและปรับขนาดได้. FPGA '17. arXiv:1612.07119

  8. Zhang, Z., Zohren, S., & Roberts, S. (2019). DeepLOB: โครงข่ายประสาทคอนโวลูชันเชิงลึกสำหรับสมุดคำสั่งซื้อขายแบบจำกัด. IEEE Transactions on Signal Processing, 67(11), 3001-3012. arXiv:1808.03668

ข้อจำกัดความรับผิดชอบ: ข้อมูลที่ให้ไว้ในบทความนี้มีไว้เพื่อการศึกษาและให้ข้อมูลเท่านั้น และไม่ถือเป็นคำแนะนำทางการเงิน การลงทุน หรือการเทรด การเทรดสกุลเงินดิจิทัลมีความเสี่ยงสูงที่จะขาดทุน

ผู้เขียน

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 เฉพาะ การวิเคราะห์ตลาด และการอัปเดตแพลตฟอร์ม

เราเคารพความเป็นส่วนตัวของคุณ ยกเลิกการสมัครได้ทุกเมื่อ