Bỏ qua

Suy luận Biên (Edge Inference)

Suy luận biên chạy mô hình trên thiết bị người dùng (điện thoại, laptop, cảm biến IoT) mà không gửi dữ liệu lên cloud. File này bao quát các ràng buộc của thiết bị biên, pipeline nén mô hình, runtime on-device, ngăn xếp trình biên dịch (compiler stack), các mục tiêu phần cứng (NPU, Neural Engine), LLM trên thiết bị, học liên kết (federated learning), và tối ưu hóa độ trễ

  • Suy luận cloud đòi hỏi kết nối mạng, thêm độ trễ (50-200 ms khứ hồi), tốn tiền mỗi yêu cầu, và gửi dữ liệu người dùng đến máy chủ bên thứ ba. Suy luận biên loại bỏ cả bốn điều này: mô hình chạy cục bộ, phản hồi tức thì, không tốn chi phí mỗi lần suy luận, và giữ dữ liệu riêng tư.

  • Sự đánh đổi: thiết bị biên có sức mạnh tính toán và bộ nhớ kém hơn 100-1000 lần so với GPU trung tâm dữ liệu. Để mô hình chạy được trong các ràng buộc này đòi hỏi tối ưu hóa mạnh mẽ ở mọi cấp độ.

  • Cactus (github.com/cactus-compute/cactus) là một engine AI độ trễ thấp được xây dựng riêng cho các thiết bị di động và đeo được. Nó minh họa nhiều kỹ thuật được đề cập trong file này trong sản xuất: kernel ARM SIMD tùy chỉnh cho các phép toán attention và ma trận (chương 16), lượng tử hóa cache KV (chương 17 file 01), chunked prefill, suy luận tăng tốc NPU trên chip Apple và Qualcomm, ánh xạ bộ nhớ zero-copy giúp giảm 10 lần sử dụng RAM, và tự động dự phòng lên cloud khi tính toán on-device không đủ. Cactus hỗ trợ suy luận đa phương thức (LLM, thị giác, giọng nói) trên iOS, Android, macOS và Linux nhúng, với SDK cho Swift, Kotlin, Python, Flutter, React Native và Rust. Các điểm chuẩn của nó cho thấy tốc độ decode 100 token/s trên M4 Pro và 48 token/s trên iPhone 17 Pro cho mô hình 1.2B ở INT4 — một ví dụ cụ thể về suy luận biên được tối ưu hóa trông như thế nào.

Các Ràng buộc của Thiết bị Biên

Tài nguyên Cloud GPU (H100) Laptop (M4) Điện thoại (Snapdragon 8 Gen 3) IoT (ESP32)
RAM 80 GB HBM3 16-36 GB unified 8-12 GB LPDDR5 520 KB
Tính toán 989 TFLOPS (FP8) 38 TOPS (Neural Engine) 45 TOPS (NPU) 0.001 TOPS
Năng lượng 700 W 15-30 W 5-10 W 0.1 W
Lưu trữ TB 256 GB-2 TB 128-512 GB 4 MB
  • Khoảng cách tính toán giữa một GPU cloud và một NPU điện thoại là ~20 lần. Giữa GPU và vi điều khiển, nó là ~1,000,000 lần. Các thiết bị khác nhau yêu cầu các mức nén và kiến trúc mô hình khác nhau.

Pipeline Nén Mô hình

  • Đối với triển khai biên, nén không phải là một kỹ thuật đơn lẻ — nó là một pipeline các kỹ thuật bổ sung được áp dụng tuần tự:
Full model (FP32, 70B params)
    ↓ Knowledge distillation → smaller model (7B params)
    ↓ Structured pruning → remove redundant heads/layers (4B effective)
    ↓ Quantisation (INT4) → 4x smaller (2 GB)
    ↓ Compiler optimisation → fused kernels, optimised memory layout
    ↓ Runtime → on-device execution
  • Mỗi bước làm giảm kích thước và độ trễ. Thứ tự quan trọng: cô đọng trước (giảm kiến trúc), sau đó tỉa bớt (loại bỏ cấu trúc), rồi lượng tử hóa (giảm độ chính xác), cuối cùng biên dịch (tối ưu cho phần cứng đích). Cô đọng sau khi lượng tử hóa sẽ cố gắng nén một mô hình đã mất mát.

Runtime On-Device

  • Runtime nạp một mô hình, cấp phát bộ nhớ và thực thi suy luận trên phần cứng đích. Mỗi nền tảng có runtime ưa thích:

  • ONNX Runtime: đa nền tảng (Windows, Linux, macOS, iOS, Android). Hỗ trợ CPU, GPU (CUDA, DirectML, CoreML, NNAPI) và nhiều backend tăng tốc. Lựa chọn portable nhất. Các mô hình được export sang định dạng ONNX từ PyTorch/TensorFlow.

  • TensorFlow Lite (TFLite): runtime biên của Google. Tối ưu cho CPU ARM và NPU Android. Binary nhỏ (~1 MB). Hỗ trợ INT8 và float16. Tiêu chuẩn cho triển khai Android.

  • Core ML: runtime của Apple cho iOS/macOS. Tự động sử dụng Neural Engine, GPU hoặc CPU tùy thuộc vào đặc tính mô hình. Mô hình được chuyển đổi từ PyTorch/TensorFlow bằng coremltools. Tích hợp chặt với phần cứng Apple (bộ nhớ unified, Neural Engine).

  • ExecuTorch: runtime mới của Meta cho PyTorch trên thiết bị. Được thiết kế cho triển khai biên với biên dịch ahead-of-time và ủy thác ở mức toán tử cho các bộ tăng tốc phần cứng. Kế thừa của PyTorch Mobile.

  • TensorRT: runtime của NVIDIA cho tối ưu hóa suy luận GPU (chương 15). Kết hợp các lớp, chọn kernel tối ưu và lượng tử hóa tự động. Nhanh hơn 2-5 lần so với PyTorch eager mode trên GPU NVIDIA.

  • llama.cpp: engine suy luận LLM C++ tệp đơn. Hỗ trợ lượng tử hóa GGUF (Q4, Q5, Q8), CPU (AVX/NEON), Metal (Apple GPU), CUDA và Vulkan. Lựa chọn hàng đầu để chạy LLM trên phần cứng người tiêu dùng.

Ngăn xếp Trình biên dịch (Compiler Stack)

  • Giữa mô hình cấp cao (đồ thị PyTorch) và phần cứng (lệnh NPU) là ngăn xếp trình biên dịch, nó tối ưu hóa mô hình cho mục tiêu cụ thể:
PyTorch model
    ↓ Export (torch.export, ONNX, TorchScript)
Graph IR (intermediate representation)
    ↓ Graph optimisations
        - Constant folding (tính biểu thức hằng số tại thời điểm biên dịch)
        - Dead code elimination (loại bỏ các phép toán không dùng)
        - Operator fusion (conv + bn + relu → một op hợp nhất)
        - Layout transformation (NCHW → NHWC cho ARM, channels-last)
    ↓ Lowering
Hardware-specific IR
    ↓ Backend optimisations
        - Tiling và loop ordering (mẫu truy cập thân thiện với cache)
        - Vectorisation (SIMD, chương 16)
        - Memory planning (tái sử dụng bộ đệm để giảm peak memory)
        - Kernel selection (chọn hiện thực tốt nhất cho mỗi toán tử)
    ↓ Code generation
Mã máy / lệnh NPU
  • Kết hợp toán tử (operator fusion) là tối ưu hóa có tác động lớn nhất. Một khối transformer có ~20 toán tử (matmul, add, layernorm, softmax, v.v.). Không có fusion, mỗi toán tử ghi output của nó vào bộ nhớ và toán tử tiếp theo đọc lại. Với fusion, nhiều toán tử được kết hợp thành một kernel duy nhất giữ dữ liệu trong thanh ghi/cache. Điều này có thể nhanh hơn 2-5 lần (chương 16, mô hình roofline).

  • Lập kế hoạch bộ nhớ (memory planning): trình biên dịch phân tích đồ thị mô hình để xác định tensor nào có vòng đời chồng lấn và có thể chia sẻ cùng một bộ đệm. Một mô hình với 100 tensor trung gian có thể chỉ cần bộ nhớ cho 10 tensor, vì hầu hết được tiêu thụ và giải phóng trước khi các tensor khác được tạo ra. Điều này rất quan trọng trên các thiết bị có RAM hạn chế.

Các Mục tiêu Phần cứng

GPU Di động

  • Qualcomm Adreno (Android): hỗ trợ OpenCL, Vulkan compute (chương 16) và SNPE (Snapdragon Neural Processing Engine) độc quyền của Qualcomm. GPU Adreno có 256-1024 ALU với hỗ trợ FP16 và INT8.

  • ARM Mali (Android): hỗ trợ OpenCL và Vulkan. GPU Mali dùng kiến trúc dựa trên tile (khác với GPU desktop), ảnh hưởng đến các mẫu truy cập bộ nhớ tối ưu.

  • Apple GPU (iOS/macOS): truy cập qua Metal (GPU API của Apple). Kiến trúc bộ nhớ unified đồng nghĩa không có chi phí sao chép CPU↔GPU. Metal Performance Shaders (MPS) cung cấp các phép toán ML tối ưu.

Bộ xử lý Neural (NPU)

  • NPU là các bộ tăng tốc chức năng cố định được thiết kế đặc biệt cho suy luận ML. Chúng tiết kiệm năng lượng hơn nhiều so với GPU cho các phép toán ML tiêu chuẩn (matmul, conv, activation).

  • Apple Neural Engine: 16 nhân, ~38 TOPS (INT8). Truy cập qua Core ML. Xuất sắc cho các mô hình thị giác và khuếch tán trên thiết bị. Không thể chạy mã tùy ý — chỉ các phép toán được Core ML hỗ trợ.

  • Qualcomm Hexagon NPU: tích hợp trong chip Snapdragon SoC. Hỗ trợ suy luận INT8 và INT4. Truy cập qua SNPE hoặc ONNX Runtime với backend QNN. Cung cấp năng lượng cho các tính năng on-device như xóa phông nền, nhận dạng giọng nói và dịch thuật thời gian thực.

  • Google Edge TPU: một phiên bản nhỏ, công suất thấp của cloud TPU. 4 TOPS, 2W. Dùng trong các thiết bị Coral cho suy luận on-device. Chỉ hỗ trợ các mô hình TFLite lượng tử hóa INT8.

  • Mẫu ủy thác (delegation pattern): runtime chia đồ thị mô hình giữa NPU (cho các phép toán được hỗ trợ) và CPU (cho các phép toán không được hỗ trợ). Tối đa hóa phần chạy trên NPU là chìa khóa cho hiệu suất và hiệu quả năng lượng.

LLM Trên Thiết bị

  • Chạy LLM trên điện thoại và laptop đã trở nên khả thi với các mô hình nhỏ và lượng tử hóa mạnh:
Mô hình Tham số Dung lượng đã lượng tử hóa Thiết bị đích Hiệu suất
Phi-3 Mini 3.8B ~2 GB (Q4) Điện thoại/Laptop ~15 token/s trên iPhone 15
Gemma 2B 2B ~1.5 GB (Q4) Điện thoại ~20 token/s trên Pixel 8
Llama 3.2 1B 1B ~700 MB (Q4) Điện thoại ~30 token/s
Llama 3.2 3B 3B ~2 GB (Q4) Điện thoại/Laptop ~15 token/s
Llama 3.1 8B 8B ~4.5 GB (Q4) Laptop ~20 token/s trên M2
  • Thách thức:

    • Bộ nhớ: mô hình 3B Q4 nằm gọn trong 2 GB, nhưng cache KV cho các hội thoại dài cộng thêm đáng kể. Độ dài ngữ cảnh thường bị giới hạn ở 2-4K token trên điện thoại.
    • Giới hạn nhiệt (thermal throttling): suy luận kéo dài làm nóng điện thoại. Sau 30 giây sinh liên tục, SoC giảm xung nhịp để tránh quá nhiệt, làm giảm hiệu suất từ 30-50%.
    • Pin: chạy mô hình 3B ở 15 token/s tiêu thụ ~3-5W. Một hội thoại 30 phút tiêu hao ~5% pin điện thoại điển hình. Chấp nhận được cho sử dụng không thường xuyên, nhưng có vấn đề cho các ứng dụng luôn bật.
  • llama.cpp là tiêu chuẩn cho LLM trên thiết bị. Nó chạy trên CPU (AVX2, NEON, I8MM), Apple GPU (Metal), NVIDIA GPU (CUDA), AMD GPU (ROCm/Vulkan), và thậm chí cả điện thoại (qua Termux trên Android).

Học Liên kết (Federated Learning)

  • Học liên kết huấn luyện mô hình trên nhiều thiết bị mà không tập trung dữ liệu. Mỗi thiết bị huấn luyện trên dữ liệu cục bộ của nó, tính toán một cập nhật gradient, và chỉ gửi cập nhật (chứ không phải dữ liệu) đến một máy chủ trung tâm để tổng hợp.

  • Thuật toán (FedAvg):

    1. Máy chủ gửi mô hình hiện tại đến \(K\) thiết bị được chọn.
    2. Mỗi thiết bị tinh chỉnh mô hình trên dữ liệu cục bộ của nó trong vài bước.
    3. Mỗi thiết bị gửi mô hình đã cập nhật (hoặc phần chênh lệch) về máy chủ.
    4. Máy chủ tính trung bình các cập nhật: \(W_{\text{new}} = \frac{1}{K} \sum_{k=1}^{K} W_k\).
    5. Lặp lại.
  • Quyền riêng tư: dữ liệu thô không bao giờ rời khỏi thiết bị. Máy chủ chỉ thấy các cập nhật mô hình tổng hợp. Quyền riêng tư vi phân (differential privacy) thêm nhiễu vào các cập nhật sao cho các điểm dữ liệu cá nhân không thể được tái tạo từ gradient.

  • Hiệu quả truyền thông: cập nhật mô hình lớn (cùng kích thước với mô hình). Các kỹ thuật nén làm giảm điều này: lượng tử hóa gradient (gửi gradient INT8 thay vì FP32), làm thưa (chỉ gửi các gradient lớn nhất), và tích lũy gradient (làm nhiều bước cục bộ, gửi ít thường xuyên hơn).

  • Ứng dụng: Dự đoán bàn phím của Google (Gboard), nhận dạng giọng nói của Apple, giám sát sức khỏe (huấn luyện trên dữ liệu sức khỏe nhạy cảm mà không tập trung hóa nó).

Tối ưu hóa Độ trễ

  • Ngoài nén, một vài kỹ thuật khác giảm độ trễ suy luận từ đầu đến cuối:

  • Thoát sớm (early exit): thêm các đầu phân loại ở các lớp trung gian. Nếu mô hình tự tin ở lớp 6 (trên tổng số 24), trả về dự đoán mà không chạy các lớp 7-24. Đầu vào dễ thoát sớm, đầu vào khó dùng toàn bộ mô hình. Độ trễ trung bình giảm đáng kể cho các tác vụ có hỗn hợp đầu vào dễ và khó.

  • Phân hoạch mô hình (model partitioning): chia mô hình giữa NPU (hiệu quả cho matmul), GPU (hiệu quả cho các phép toán không đều), và CPU (xử lý mọi thứ khác). Trình biên dịch quyết định phép toán nào đi đâu dựa trên hồ sơ hiệu suất.

  • Lưu cache: cho các ứng dụng có truy vấn lặp lại (tự động hoàn thành, code completion), cache các tính toán gần đây. Nếu người dùng gõ "Làm thế nào để" và mô hình gần đây đã sinh phần hoàn thành cho "Làm thế nào để", cache KV đã lưu có thể được tái sử dụng, bỏ qua hoàn toàn giai đoạn prefill.

  • Lấy trước suy đoán (speculative prefetching): dự đoán người dùng sẽ làm gì tiếp theo và bắt đầu suy luận trước khi họ yêu cầu. Một ứng dụng chat có thể bắt đầu sinh phản hồi cho câu hỏi tiếp theo có khả năng xảy ra trong khi người dùng đang đọc câu trả lời hiện tại.

Bài tập lập trình (dùng CoLab hoặc notebook)

  1. Mô phỏng pipeline nén mô hình. Bắt đầu với mô hình float32, áp dụng cô đọng (mô phỏng), tỉa bớt và lượng tử hóa, và theo dõi kích thước ở mỗi bước.
def compression_pipeline(original_params_M, original_bits=32):
    size_mb = original_params_M * 1e6 * original_bits / 8 / 1e6

    print(f"Original: {original_params_M}M params, {original_bits}-bit → {size_mb:.0f} MB")

    # Step 1: Knowledge distillation (reduce params)
    distilled_params = original_params_M * 0.15  # 70B → ~10B equivalent
    size_mb = distilled_params * 1e6 * original_bits / 8 / 1e6
    print(f"After distillation ({distilled_params:.0f}M params): {size_mb:.0f} MB")

    # Step 2: Structured pruning (remove 30% of remaining)
    pruned_params = distilled_params * 0.7
    size_mb = pruned_params * 1e6 * original_bits / 8 / 1e6
    print(f"After pruning ({pruned_params:.0f}M params): {size_mb:.0f} MB")

    # Step 3: INT4 quantisation
    size_mb = pruned_params * 1e6 * 4 / 8 / 1e6
    print(f"After INT4 quantisation: {size_mb:.0f} MB")

    print(f"Total compression: {original_params_M * 1e6 * original_bits / 8 / 1e6 / size_mb:.0f}x")

print("=== Starting from 70B model ===")
compression_pipeline(70000)

print("\n=== Starting from 7B model ===")
compression_pipeline(7000)
  1. Ước tính độ trễ suy luận trên thiết bị. Với số lượng phép toán của mô hình và thông số phần cứng, tính toán xem nó có đáp ứng mục tiêu độ trễ không.
def estimate_latency(model_name, params_M, bits, compute_tops, mem_bw_gbs, seq_len=256):
    """Estimate token generation latency for a memory-bandwidth-bound model."""
    # Model size in bytes
    model_bytes = params_M * 1e6 * bits / 8

    # Decode is memory-bound: must load entire model per token
    time_per_token_ms = model_bytes / (mem_bw_gbs * 1e9) * 1000

    # Tokens per second
    tokens_per_sec = 1000 / time_per_token_ms

    print(f"{model_name}: {params_M/1000:.1f}B params @ {bits}-bit = {model_bytes/1e9:.1f} GB")
    print(f"  Memory bandwidth: {mem_bw_gbs} GB/s")
    print(f"  Time per token: {time_per_token_ms:.1f} ms")
    print(f"  Tokens/sec: {tokens_per_sec:.0f}")
    print()

# Apple M2 Pro: 200 GB/s unified memory bandwidth
print("=== Apple M2 Pro (200 GB/s) ===")
estimate_latency("Llama-7B Q4", 7000, 4, 15.8, 200)
estimate_latency("Llama-7B Q8", 7000, 8, 15.8, 200)
estimate_latency("Llama-70B Q4", 70000, 4, 15.8, 200)

# Phone (Snapdragon 8 Gen 3): ~50 GB/s LPDDR5
print("=== Snapdragon 8 Gen 3 (50 GB/s) ===")
estimate_latency("Phi-3 Mini Q4", 3800, 4, 45, 50)
estimate_latency("Llama-3B Q4", 3000, 4, 45, 50)