Mục lục
- Mục tiêu bài học
- ONNX là gì
- TensorRT là gì
- Workflow tổng quát
- Export PyTorch → ONNX
- Export Hugging Face Transformers → ONNX
- Chạy model với ONNX Runtime
- Verify accuracy sau export
- Benchmark tốc độ
- TensorRT — compile ONNX thành engine
- TensorRT FP16 và INT8
- TensorRT Python inference
- Triton Inference Server với TensorRT
- TensorRT-LLM — cho LLM
- Bảng speedup thực tế
- Khi nào chọn ONNX Runtime hay TensorRT
- Common pitfalls
Mục tiêu bài học
Sau bài này bạn sẽ:
- Hiểu ONNX là format gì và tại sao cần nó trong deployment pipeline
- Export model PyTorch và Hugging Face sang ONNX đúng cách với
dynamic_axes - Chạy ONNX model bằng ONNX Runtime trên CPU và CUDA
- Compile ONNX sang TensorRT engine FP16/INT8
- Biết khi nào dùng ONNX Runtime, TensorRT, OpenVINO, CoreML
- Nắm các pitfalls hay gặp khi export và deploy
ONNX là gì
Open Neural Network Exchange (ONNX) là format file chuẩn mở để lưu model neural network. Microsoft, Meta và Amazon khởi xướng năm 2017; hiện có hơn 50 framework và runtime hỗ trợ.
ONNX mô tả model dưới dạng computation graph: mỗi node là một operator (Conv, MatMul, ReLU…), mỗi edge là tensor. File .onnx là protobuf binary chứa graph + weight.
Framework có thể export sang ONNX
- PyTorch —
torch.onnx.export()(built-in) - TensorFlow / Keras —
tf2onnx - Hugging Face Transformers —
optimumCLI và Python API - scikit-learn —
sklearn-onnx - XGBoost, LightGBM —
onnxmltools
Runtime có thể đọc ONNX
- ONNX Runtime — cross-platform, CPU và GPU (CUDA, DirectML, ROCm)
- TensorRT — NVIDIA GPU
- OpenVINO — Intel CPU, iGPU, VPU
- CoreML — Apple Silicon, iOS, macOS
- NNAPI / TFLite — Android
Khi nào cần ONNX
Trường hợp phổ biến nhất: train với PyTorch, nhưng muốn serve bằng runtime tối ưu hơn (ONNX Runtime, TensorRT) hoặc deploy trên hardware không có PyTorch (edge device, mobile, cloud inference endpoint tối thiểu hóa dependency).
TensorRT là gì
TensorRT là SDK của NVIDIA để tối ưu và chạy deep learning inference trên GPU NVIDIA. TensorRT không phải runtime đơn thuần — nó là compiler: nhận ONNX (hoặc model PyTorch, TF) rồi tạo ra engine binary tối ưu cho GPU cụ thể.
Cơ chế TensorRT hoạt động
- Layer fusion: gộp nhiều operator liền nhau (Conv + BN + ReLU) thành 1 kernel, giảm roundtrip bộ nhớ GPU
- Precision calibration: hỗ trợ FP32, FP16, INT8 — INT8 cần calibration data
- Kernel auto-tuning: chọn kernel implementation tốt nhất cho GPU cụ thể (T4, A100, H100…)
- Memory optimization: tái sử dụng tensor buffer trong graph
Trade-off cần biết trước
- Engine binary lock vào GPU type: compile trên T4 thì chỉ chạy trên T4; deploy GPU khác phải compile lại
- Build engine mất vài phút đến hàng chục phút tùy model và GPU
- Chỉ chạy trên NVIDIA GPU — không có fallback CPU
- Hỗ trợ dynamic shape nhưng phải khai báo tường minh trong config
TensorRT 10.x (phát hành 2024) có Python API ổn định hơn; các ví dụ dưới dùng TensorRT 10.x.
Workflow tổng quát
PyTorch model
│
├─── torch.onnx.export() ──→ model.onnx
│ │
│ ├──→ ONNX Runtime (CPU / CUDA / DirectML)
│ │
│ ├──→ TensorRT engine (NVIDIA GPU only)
│ │
│ ├──→ OpenVINO (Intel CPU / iGPU)
│ │
│ └──→ CoreML (Apple Silicon, iOS)
│
└─── TensorRT Python API ──→ TensorRT engine (direct, không qua ONNX)
Con đường phổ biến nhất trong production là PyTorch → ONNX → ONNX Runtime hoặc TensorRT. Bước ONNX trung gian giúp decouple training framework khỏi serving runtime.
Export PyTorch → ONNX
import torch
import torchvision.models as models
model = models.resnet18(weights="IMAGENET1K_V1").eval()
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
model,
dummy_input,
"resnet18.onnx",
export_params=True, # lưu weight vào file
opset_version=17, # ONNX opset — dùng 17+ cho model 2023 trở về sau
do_constant_folding=True, # fold constant operation lúc export
input_names=["input"],
output_names=["output"],
dynamic_axes={
"input": {0: "batch_size"}, # batch dim là variable
"output": {0: "batch_size"},
},
)
print("Exported resnet18.onnx")
Các tham số quan trọng
opset_version: ONNX định nghĩa operator theo opset. Opset 17 là stable cho hầu hết model 2024. Nếu runtime báo lỗi operator không hỗ trợ, thử hạ xuống 13 hoặc 15.dynamic_axes: không khai báo thì engine sẽ fix shape = shape củadummy_input. Trong production gần như luôn cần batch variable — khai báo tường minh.do_constant_folding: pre-compute node có output là constant (vd bias, BN statistics) lúc export. Giảm nhẹ graph size.
Verify file vừa export
import onnx
model_onnx = onnx.load("resnet18.onnx")
onnx.checker.check_model(model_onnx)
print(f"ONNX opset: {model_onnx.opset_import[0].version}")
print(f"Input: {[i.name for i in model_onnx.graph.input]}")
print(f"Output: {[o.name for o in model_onnx.graph.output]}")
onnx.checker.check_model() raise ValidationError nếu graph không hợp lệ. Chạy bước này trước khi tiếp tục.
Export Hugging Face Transformers → ONNX
Transformer model có dynamic input (token length variable) và nhiều output — export thủ công với torch.onnx.export khá phức tạp. Thư viện Optimum của Hugging Face tự động hóa bước này.
Cài đặt
pip install "optimum[exporters]" # optimum 1.20+
Export qua CLI
# Export DistilBERT cho classification
optimum-cli export onnx \
--model distilbert-base-uncased-finetuned-sst-2-english \
--task text-classification \
distilbert_onnx/
# Export BERT cho feature extraction
optimum-cli export onnx \
--model bert-base-uncased \
--task feature-extraction \
bert_onnx/
Flag --task quan trọng — Optimum dùng nó để biết cần bao gồm head nào trong graph.
Export và chạy ngay trong Python
from optimum.onnxruntime import ORTModelForSequenceClassification
from transformers import AutoTokenizer
# Export khi load lần đầu (export=True), lưu vào thư mục
model = ORTModelForSequenceClassification.from_pretrained(
"distilbert-base-uncased-finetuned-sst-2-english",
export=True,
)
model.save_pretrained("./distilbert_onnx")
tokenizer = AutoTokenizer.from_pretrained(
"distilbert-base-uncased-finetuned-sst-2-english"
)
inputs = tokenizer("I love this movie", return_tensors="pt")
outputs = model(**inputs)
print(outputs.logits)
Lần sau chỉ cần from_pretrained("./distilbert_onnx") — không cần export=True nữa. ORTModelForXxx có interface giống AutoModelForXxx, drop-in replacement trong pipeline.
Chạy model với ONNX Runtime
import onnxruntime as ort # onnxruntime 1.18+
import numpy as np
# Khai báo provider theo thứ tự ưu tiên
# ONNX Runtime sẽ dùng provider đầu tiên available
session = ort.InferenceSession(
"resnet18.onnx",
providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
)
# Kiểm tra provider nào đang được dùng
print(session.get_providers())
# Lấy tên input/output từ session
input_name = session.get_inputs()[0].name # "input"
output_name = session.get_outputs()[0].name # "output"
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
outputs = session.run([output_name], {input_name: input_data})
print(outputs[0].shape) # (1, 1000)
Execution Providers
- CUDAExecutionProvider — NVIDIA GPU, cần
onnxruntime-gpu - TensorrtExecutionProvider — NVIDIA GPU qua TensorRT, cần cả TensorRT cài sẵn
- CPUExecutionProvider — CPU, luôn có, là fallback
- OpenVINOExecutionProvider — Intel hardware
- CoreMLExecutionProvider — Apple Silicon, macOS / iOS
Nếu CUDAExecutionProvider không available (không có GPU hoặc cài onnxruntime thay vì onnxruntime-gpu), ONNX Runtime tự fallback sang CPUExecutionProvider. Không báo lỗi — cần log session.get_providers() để confirm.
Session Options để tune
opts = ort.SessionOptions()
opts.intra_op_num_threads = 4 # số thread cho 1 op
opts.inter_op_num_threads = 1 # số thread song song giữa các op
opts.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
session = ort.InferenceSession(
"resnet18.onnx",
sess_options=opts,
providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
)
Verify accuracy sau export
Sau khi export, output của ONNX Runtime phải khớp với PyTorch trong tolerance số học (FP32 có sai số nhỏ do thứ tự operation).
import torch
import numpy as np
# PyTorch forward pass
dummy_input = torch.randn(1, 3, 224, 224)
with torch.no_grad():
torch_output = model(dummy_input).numpy()
# ONNX Runtime forward pass
ort_output = session.run(
[output_name],
{input_name: dummy_input.numpy()},
)[0]
# So sánh
np.testing.assert_allclose(
torch_output,
ort_output,
rtol=1e-3, # relative tolerance
atol=1e-5, # absolute tolerance
)
print("Output matches within tolerance")
# Xem max diff để đánh giá
max_diff = np.abs(torch_output - ort_output).max()
print(f"Max absolute difference: {max_diff:.2e}")
Nếu diff lớn hơn expected (>1e-2): thường do mixed precision trong model hoặc custom operation. Thử tắt do_constant_folding hoặc đổi opset_version.
Với INT8 quantized ONNX model, tolerance cần nới rộng hơn (rtol=1e-1) — accuracy giảm là expected. Cần eval trên validation set thay vì chỉ so sánh output 1 sample.
Benchmark tốc độ
import time
import torch
import numpy as np
def benchmark(forward_fn, input_data, n: int = 100, warmup: int = 10) -> float:
"""Trả về latency trung bình (ms) trên n lần chạy."""
for _ in range(warmup):
forward_fn(input_data)
start = time.perf_counter()
for _ in range(n):
forward_fn(input_data)
elapsed = (time.perf_counter() - start) / n * 1000
return elapsed
input_np = np.random.randn(1, 3, 224, 224).astype(np.float32)
# PyTorch CPU
with torch.no_grad():
torch_latency = benchmark(
lambda x: model(torch.from_numpy(x)),
input_np,
)
# ONNX Runtime
ort_latency = benchmark(
lambda x: session.run(None, {input_name: x}),
input_np,
)
print(f"PyTorch CPU: {torch_latency:.2f} ms")
print(f"ONNX Runtime: {ort_latency:.2f} ms")
print(f"Speedup: {torch_latency / ort_latency:.2f}x")
Lưu ý khi benchmark GPU: cần thêm torch.cuda.synchronize() (PyTorch) và xài CUDA event để đo chính xác, không phải time.perf_counter(). CPU benchmark với time.perf_counter() là đủ.
Trên CPU, ONNX Runtime thường nhanh hơn PyTorch 1.5–2x với model classification. Khoảng cách lớn hơn nếu model nhiều element-wise operation (ONNX Runtime fuse tốt hơn).
TensorRT — compile ONNX thành engine
Cách nhanh nhất: trtexec CLI
# FP32
trtexec --onnx=resnet18.onnx --saveEngine=resnet18_fp32.engine
# FP16 — gần như không mất accuracy, speedup ~1.5-2x so với FP32
trtexec --onnx=resnet18.onnx --saveEngine=resnet18_fp16.engine --fp16
# Dynamic shape — khai báo minShapes/optShapes/maxShapes
trtexec \
--onnx=resnet18.onnx \
--saveEngine=resnet18_dynamic.engine \
--fp16 \
--minShapes=input:1x3x224x224 \
--optShapes=input:8x3x224x224 \
--maxShapes=input:32x3x224x224
trtexec đi kèm với TensorRT installation (/usr/src/tensorrt/bin/trtexec hoặc trong container nvcr.io/nvidia/tensorrt). Đây là cách đủ dùng cho phần lớn trường hợp không cần custom config.
Python API — khi cần kiểm soát chi tiết
import tensorrt as trt # tensorrt 10.x
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(
1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
)
parser = trt.OnnxParser(network, TRT_LOGGER)
with open("resnet18.onnx", "rb") as f:
if not parser.parse(f.read()):
for i in range(parser.num_errors):
print(parser.get_error(i))
raise RuntimeError("ONNX parse failed")
config = builder.create_builder_config()
config.set_flag(trt.BuilderFlag.FP16)
# 1GB workspace cho optimization
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30)
# Dynamic shape profile
profile = builder.create_optimization_profile()
profile.set_shape(
"input",
min=(1, 3, 224, 224),
opt=(8, 3, 224, 224),
max=(32, 3, 224, 224),
)
config.add_optimization_profile(profile)
engine_bytes = builder.build_serialized_network(network, config)
with open("resnet18.engine", "wb") as f:
f.write(engine_bytes)
print("Engine saved")
Build engine có thể mất 5–20 phút tùy model và GPU. Nên chạy một lần lúc CI/CD, không chạy lúc startup service.
TensorRT FP16 và INT8
FP16 — gần như miễn phí
Chỉ cần thêm config.set_flag(trt.BuilderFlag.FP16). TensorRT tự quyết định layer nào convert sang FP16, layer nào giữ FP32. Accuracy gần như không giảm trên classification model; speedup 1.5–2x so với FP32 trên GPU có Tensor Core (T4, A100, H100).
INT8 — cần calibration data
INT8 yêu cầu calibrator: một tập data đại diện để TensorRT học range của activation và chọn scale factor tối ưu.
import tensorrt as trt
import numpy as np
class Int8EntropyCalibrator(trt.IInt8EntropyCalibrator2):
"""
Calibrator dùng IInt8EntropyCalibrator2 (entropy-based,
thường tốt hơn MinMax cho classification).
"""
def __init__(self, calib_data: np.ndarray, cache_file: str = "calib.cache"):
super().__init__()
self._data = calib_data # shape: (N, C, H, W), float32
self._idx = 0
self._cache = cache_file
# Allocate GPU buffer
import pycuda.driver as cuda
import pycuda.autoinit # noqa: F401
self._buf = cuda.mem_alloc(calib_data[0].nbytes)
def get_batch_size(self) -> int:
return 1
def get_batch(self, names):
if self._idx >= len(self._data):
return None
import pycuda.driver as cuda
cuda.memcpy_htod(self._buf, np.ascontiguousarray(self._data[self._idx]))
self._idx += 1
return [int(self._buf)]
def read_calibration_cache(self):
if self._cache and open(self._cache, "rb").read() if __import__("os").path.exists(self._cache) else None:
with open(self._cache, "rb") as f:
return f.read()
def write_calibration_cache(self, cache):
with open(self._cache, "wb") as f:
f.write(cache)
# Sử dụng
calib_data = np.random.randn(100, 3, 224, 224).astype(np.float32) # dùng real data
calibrator = Int8EntropyCalibrator(calib_data)
config.set_flag(trt.BuilderFlag.INT8)
config.int8_calibrator = calibrator
Calibration data cần representative: nếu dùng data không đại diện cho distribution production, scale factor sai, accuracy giảm đáng kể. Tối thiểu 100–500 sample, lý tưởng 1000.
TensorRT lưu cache calibration (calib.cache). Lần sau build engine với cùng model thì đọc lại cache — không cần chạy calibration lại từ đầu nếu model không thay đổi.
TensorRT Python inference
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit # noqa: F401
import numpy as np
# Load engine
runtime = trt.Runtime(trt.Logger(trt.Logger.WARNING))
with open("resnet18.engine", "rb") as f:
engine = runtime.deserialize_cuda_engine(f.read())
context = engine.create_execution_context()
# Allocate I/O buffers (một lần lúc init)
input_shape = (1, 3, 224, 224)
output_shape = (1, 1000)
h_input = np.random.randn(*input_shape).astype(np.float32)
h_output = np.empty(output_shape, dtype=np.float32)
d_input = cuda.mem_alloc(h_input.nbytes)
d_output = cuda.mem_alloc(h_output.nbytes)
stream = cuda.Stream()
# Inference
cuda.memcpy_htod_async(d_input, h_input, stream)
context.execute_async_v2(
bindings=[int(d_input), int(d_output)],
stream_handle=stream.handle,
)
cuda.memcpy_dtoh_async(h_output, d_output, stream)
stream.synchronize()
print(h_output.argmax())
TensorRT Python API verbose hơn ONNX Runtime vì phải tự quản lý GPU memory. Trong production, gần như không ai viết inference loop này trực tiếp — thay vào đó dùng Triton Inference Server (mục tiếp theo) hoặc wrap bằng thư viện như torch_tensorrt.
torch-tensorrt — ít code hơn
import torch
import torch_tensorrt # torch-tensorrt 2.x
# Compile trực tiếp từ PyTorch model, không qua ONNX
trt_model = torch_tensorrt.compile(
model,
inputs=[torch_tensorrt.Input(
min_shape=[1, 3, 224, 224],
opt_shape=[8, 3, 224, 224],
max_shape=[32, 3, 224, 224],
dtype=torch.float32,
)],
enabled_precisions={torch.float16},
)
# Inference như PyTorch thường
with torch.no_grad():
output = trt_model(torch.randn(1, 3, 224, 224).cuda())
torch_tensorrt giữ interface PyTorch, compile ngầm sang TensorRT. Phù hợp nếu code base đã dùng PyTorch và không muốn đổi sang pycuda.
Triton Inference Server với TensorRT
Triton Inference Server (NVIDIA) là pattern production chuẩn để serve TensorRT engine — không dùng Python inference loop thủ công như mục trước.
Cấu trúc model repository
model_repository/
└── resnet18/
├── config.pbtxt # cấu hình model
└── 1/
└── model.plan # file .engine đổi tên thành model.plan
# config.pbtxt
name: "resnet18"
platform: "tensorrt_plan"
max_batch_size: 32
input [
{
name: "input"
data_type: TYPE_FP32
dims: [3, 224, 224]
}
]
output [
{
name: "output"
data_type: TYPE_FP32
dims: [1000]
}
]
dynamic_batching {
preferred_batch_size: [8, 16]
max_queue_delay_microseconds: 5000
}
Khởi động server
docker run --gpus all --rm \
-p 8000:8000 -p 8001:8001 \
-v $(pwd)/model_repository:/models \
nvcr.io/nvidia/tritonserver:24.05-py3 \
tritonserver --model-repository=/models
Client gọi HTTP
import tritonclient.http as httpclient
import numpy as np
client = httpclient.InferenceServerClient("localhost:8000")
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
inputs = [httpclient.InferInput("input", input_data.shape, "FP32")]
outputs = [httpclient.InferRequestedOutput("output")]
inputs[0].set_data_from_numpy(input_data)
result = client.infer("resnet18", inputs, outputs=outputs)
print(result.as_numpy("output").argmax())
Triton tự xử lý dynamic batching (gom request đến trong window max_queue_delay_microseconds thành 1 batch), concurrent execution trên nhiều GPU, và health check endpoint. Đây là lý do production không viết inference server tự tay.
TensorRT-LLM — cho LLM
TensorRT thông thường không tối ưu được LLM vì LLM có KV cache, autoregressive generation, và shape thay đổi từng token. NVIDIA cung cấp thư viện riêng: TensorRT-LLM.
- Hỗ trợ sẵn Llama 2/3, Mistral, Falcon, GPT-J, Gemma, Phi… (danh sách mở rộng mỗi release)
- Tích hợp continuous batching và PagedAttention — tương tự vLLM
- Hỗ trợ tensor parallelism cho multi-GPU
- Backend native cho Triton Inference Server
# Build LLaMA 3 8B từ HuggingFace weights
python convert_checkpoint.py \
--model_dir ./Meta-Llama-3-8B \
--output_dir ./llama3-trtllm \
--dtype float16
trtllm-build \
--checkpoint_dir ./llama3-trtllm \
--output_dir ./llama3-engine \
--gemm_plugin float16 \
--max_batch_size 8 \
--max_input_len 2048 \
--max_seq_len 4096
TensorRT-LLM vs vLLM: Cả hai đều dùng PagedAttention. TensorRT-LLM thường nhanh hơn vài phần trăm đến 20% tùy model và batch size; nhưng setup phức tạp hơn, lock vào NVIDIA, compile lại khi đổi GPU. vLLM đơn giản hơn, hỗ trợ AMD GPU, AWS Inferentia. Với team nhỏ không có dedicated MLOps, vLLM thường là lựa chọn thực tế hơn.
Bảng speedup thực tế
Số liệu bên dưới từ benchmark ResNet-50, batch=1, trên T4 và A100 80GB. Kết quả cụ thể sẽ khác theo model architecture và batch size.
| Runtime / Precision | GPU | Latency (ms) | Speedup vs PyTorch FP32 |
|---|---|---|---|
| PyTorch FP32 | T4 | ~12 | 1x (baseline) |
| ONNX Runtime CUDA FP32 | T4 | ~8 | ~1.5x |
| TensorRT FP32 | T4 | ~6 | ~2x |
| TensorRT FP16 | T4 | ~3.5 | ~3.5x |
| TensorRT INT8 | T4 | ~2 | ~6x |
| TensorRT FP16 | A100 | ~1.2 | ~10x vs T4 baseline |
| TensorRT INT8 | A100 | ~0.8 | ~15x vs T4 baseline |
Speedup từ FP16 có được nhờ Tensor Core trên GPU NVIDIA từ Volta trở về sau (T4, A100, H100). GPU cũ (P100, K80) không có Tensor Core — FP16 speedup rất nhỏ hoặc không đáng kể.
Khi nào chọn ONNX Runtime hay TensorRT
| Tiêu chí | ONNX Runtime | TensorRT |
|---|---|---|
| Hardware | CPU, NVIDIA GPU, AMD GPU, Intel, Apple Silicon | NVIDIA GPU only |
| Portability | Cùng file .onnx chạy trên mọi hardware | Engine binary lock vào GPU type |
| Build time | Không cần build — load và chạy ngay | 5–30 phút compile engine |
| Peak performance | Tốt (1.5–2x vs PyTorch) | Tốt nhất trên NVIDIA (2–10x) |
| Dynamic shape | Native, không cần config thêm | Cần khai báo min/opt/max shape |
| INT8 support | Có (qua ONNX quantization tool) | Có, cần calibration data |
| Khi nào dùng | Cross-platform, dev nhanh, CPU inference, edge device | Max throughput, SLA latency nghiêm ngặt, production NVIDIA GPU |
Các trường hợp khác
- OpenVINO: deploy trên Intel CPU (server không có GPU, edge gateway), iGPU Intel. Thường nhanh hơn ONNX Runtime 1.5–3x trên Intel hardware.
- CoreML: iOS app, macOS app, Apple Silicon. Apple Neural Engine cho speedup đáng kể trên M1/M2/M3.
- GGUF + llama.cpp: LLM inference trên CPU (không cần CUDA), đã bàn trong bài 55.
Common pitfalls
-
Export ONNX với batch cố định. Nếu không khai báo
dynamic_axes, engine fix shape theodummy_input. Khi production cần batch khác phải re-export. Luôn khai báodynamic_axescho dimension cần flexible. - Opset version không tương thích. Opset 11 hoặc 12 đủ cho nhiều model cũ, nhưng TensorRT 10.x và ONNX Runtime 1.18+ khuyến nghị opset 17. Nếu runtime báo lỗi no implementation for operator X at opset Y, kiểm tra lại opset.
- TensorRT engine không portable. Engine compile trên T4 không load được trên A100 và ngược lại. Cần build pipeline riêng cho từng GPU type trong fleet.
-
Dynamic shape trong TensorRT chưa khai báo. Nếu ONNX model có dynamic batch nhưng TensorRT config không set
optimization_profile, engine sẽ fix shape hoặc báo lỗi lúc inference. Dùng--minShapes/--optShapes/--maxShapesvới trtexec hoặcset_shape()trong Python API. - INT8 calibration data không representative. Scale factor tính từ data không đúng distribution production → activation clip sai → accuracy giảm 5–15%. Dùng ít nhất vài trăm sample từ production data, không dùng random noise.
-
Custom PyTorch operator không có trong ONNX opset. Một số custom layer (custom attention variant, custom normalization) không map được sang ONNX standard op. Export sẽ fail hoặc fallback sang
ATenop mà runtime không hỗ trợ. Cần register custom op hoặc refactor layer dùng standard op. -
ONNX Runtime trên Mac M1/M2 với CUDAExecutionProvider. Mac không có CUDA; ONNX Runtime fallback CPU nhưng không báo lỗi. Dùng
CoreMLExecutionProviderđể tận dụng Apple Neural Engine. -
Không verify accuracy sau export. Export thành công không có nghĩa output đúng. Luôn chạy
np.testing.assert_allclosevới vài sample và eval trên validation set trước khi deploy. - Build TensorRT engine lúc startup service. Build mất nhiều phút, service sẽ không sẵn sàng trong thời gian đó. Build trong CI/CD pipeline, lưu engine artifact, load pre-built engine khi startup.
