YOLOE deep dive (tiếng Việt): nhận diện không cần retrain class mới

YOLOE giải thích chi tiết: cách nhận diện object mới mà không cần retrain. Phân tích 3 module RepRTA, SAVPE, LRPC qua câu chuyện thực tế nhận diện logo.

YOLOE deep dive (tiếng Việt): nhận diện không cần retrain class mới

Hồi 2024 mình nhận một dự án nhận diện logo quảng cáo trong video. Bài toán nghe đơn giản: cho một video TVC hay một livestream, output ra danh sách brand xuất hiện, kèm timestamp và bounding box. Khách hàng dùng để đo brand exposure cho campaign quảng cáo.

Mình ship được. Nhưng có một chỗ trong pipeline lúc đó luôn làm mình khó chịu, là bước onboard brand mới. Mỗi lần khách add một thương hiệu chưa có trong tập class, team ML phải gom data, label, retrain model, eval lại. Trung bình mất 2 đến 3 ngày cho mỗi brand mới. Với một khách hàng cỡ FMCG có hàng chục SKU thì pipeline biến thành dây chuyền retrain liên tục.

Pipeline retrain liên tục mỗi khi có brand mới

Lúc đó mình đã đặt một câu hỏi:

"Bên face recognition người ta giải bằng similarity search, một ảnh khuôn mặt mới là dùng được luôn, không retrain. Tại sao object detection không làm được như vậy?"

Đây hóa ra là một câu hỏi đúng câu hỏi, mặc dù lúc đó mình không biết. Mình vừa mô tả đúng vấn đề mà cả ngành computer vision đang vật lộn ba năm nay, đồng thời nêu đúng intuition về giải pháp: hãy treat object detection như similarity matching thay vì như classification cố định. Đó là toàn bộ tinh thần của hướng nghiên cứu open-vocabulary object detection.

Vấn đề là dự án chạy gấp, mình không có thời gian đào sâu lý do tại sao YOLO không làm được như face recognition, hay liệu có hướng nào tránh retrain không. Mình ship version retrain cho khách, đóng laptop, và để câu hỏi đó treo lơ lửng trong đầu khoảng hai năm.

Tuần trước mình đọc YOLOE, một paper ICCV 2025 từ Tsinghua (Thanh Hoas), và phát hiện ra paper này có vẻ đang giải đúng bài toán mình đã hỏi từ 2024. Bài viết này là cách mình giải thích lại YOLOE cho mình của hai năm trước, và cho bạn nếu bạn cũng đang lăn tăn câu chuyện tương tự. Mình sẽ cố giải thích chính xác về mặt khoa học nhưng đủ chậm để người chưa làm computer vision cũng theo được.

Tại sao face recognition làm được mà YOLO truyền thống không?

Câu hỏi này nghe đơn giản nhưng đáng dừng lại 5 phút.

Trong face recognition, model không học "phân loại từng người". Nó học một hàm $f$ biến mỗi khuôn mặt thành một vector 512 chiều, sao cho hai ảnh cùng một người cho ra hai vector gần nhau, hai người khác nhau cho ra hai vector xa nhau. Cụ thể với ArcFace hay FaceNet, "gần" được đo bằng cosine similarity. Khi có một nhân viên mới, bạn không retrain gì cả, chỉ chụp một ảnh, đẩy qua $f$, lưu vector đó vào database. Lần sau ảnh nào có vector đủ gần là người đó. (Mình từng viết một bài chi tiết về hệ thống nhận diện khuôn mặt cho ai muốn đào sâu phần này.)

YOLO truyền thống làm hoàn toàn khác. Classification head của YOLO là một lớp convolution $1 \times 1$ với số kênh đầu ra bằng đúng số class. Nếu bạn train YOLO cho 80 class COCO thì kernel có shape $80 \times D$ (với $D$ là số chiều feature). Muốn thêm class thứ 81 là "logo Coca-Cola" thì shape kernel phải đổi thành $81 \times D$, và bạn phải retrain hoặc ít nhất là fine-tune cái kernel mới này với dữ liệu logo. Đây là cấu trúc closed-set: số class được hard-code vào kiến trúc.

Vấn đề cốt lõi: YOLO trộn chung hai việc, "phát hiện chỗ nào có object" và "đặt tên cho object đó", vào cùng một head. Face recognition tách rời hai việc này. Hướng đi của open-vocabulary detection cũng vậy: tách phần "đặt tên" ra khỏi kiến trúc, biến nó thành một phép so sánh giữa hai vector embedding.

Face recognition (vector storage động) so với YOLO closed-set (grid class cố định)

Đến đây thì câu hỏi hiển nhiên là: tại sao chuyện này đến giờ mới xong xuôi? Câu trả lời thẳng thắn là tại vì các giải pháp trước YOLOE đều phải đánh đổi cái gì đó.

YOLO-World (CVPR 2024) dùng CLIP text encoder làm "bank" embedding cho category names, nhưng phải fuse text với image features ở mọi anchor point bằng cross-modality attention. Vocab càng to, inference càng chậm. T-Rex2 hỗ trợ visual prompt (đưa một ảnh mẫu, tìm những object tương tự), đẹp về ý tưởng nhưng dùng deformable attention transformer, không deploy lên iPhone hay Jetson Nano nổi. GenerateU và DINO-X đi xa hơn nữa với prompt-free mode (model tự tìm và tự đặt tên cho mọi object), nhưng phải nhét hẳn một language model bên trong (FlanT5 250M, OPT 125M), kết quả là 0.4 đến 0.5 FPS trên T4, tức là một frame mất hơn hai giây để xử lý. Còn xa mới đạt được mức realtime.

YOLOE chốt cả ba việc này trong một mô hình duy nhất. YOLOE-v8-L có 45 triệu tham số, chạy 102 FPS trên T4 với TensorRT, 27 FPS trên iPhone 12 với CoreML. Bản nhỏ YOLOE-v8-S chỉ 12 triệu tham số, đạt 305 FPS trên T4 và 64 FPS trên iPhone. So với YOLO-Worldv2, training tiết kiệm ba lần thời gian, inference nhanh hơn 1.4 lần, và LVIS AP cao hơn 3.5 điểm. Số liệu sau cùng quan trọng nhất: chạy được realtime trên thiết bị bạn đang cầm trong túi.

So sánh YOLOE với YOLO-Worldv2

Phần còn lại của bài này là ba trick toán mà YOLOE dùng để đạt được con số đó. Mỗi trick gắn với một chế độ prompt khác nhau, và mỗi cái đều đẹp theo kiểu riêng.

Kiến trúc tổng thể: chia thành 3 pipeline

Trước khi đi vào ba trick, mình tóm tắt kiến trúc YOLOE ở mức cao nhất, và giải thích từng phần một cho người chưa làm computer vision.

Đây là phiên bản đơn giản hóa của YOLOE, vẽ lại để dễ nhìn:

Sơ đồ đơn giản hóa: ảnh đi qua Backbone+PAN ra ba mức feature P3/P4/P5, rồi qua ba head (Where/Shape/What feature). Đầu ra "What feature" gặp Prompt Embedding ở phép nhân để ra Label

Hãy nhìn YOLOE như một dây chuyền có ba pipeline cùng đổ về một điểm:

  • Pipeline 1 (ảnh): từ pixel thô đi qua một loạt CNN để ra "object embedding" $\mathcal{O}$, một vector mô tả đặc trưng cho mỗi vị trí trong ảnh.
  • Pipeline 2 (prompt): từ thứ bạn cung cấp (text, ảnh mẫu, hoặc không gì cả) đi qua một module riêng để ra "prompt embedding" $\mathcal{P}$, một vector mô tả category bạn quan tâm.
  • Điểm gặp: nhân hai vector lại bằng tích vô hướng để ra score, score cao nghĩa là anchor đó match với category đó.

Đây là toàn bộ kiến trúc đầy đủ từ paper gốc, dày hơn vì có cả ba module prompt được vẽ chi tiết. Bạn không cần đọc kỹ ngay bây giờ, chỉ cần dùng làm tham chiếu khi mình giải thích từng phần ở dưới:

Pipeline YOLOE đầy đủ từ paper: phía trên là pipeline ảnh, phía dưới và bên phải là ba module prompt (SAVPE, RepRTA, LRPC)

Tách ra từng phần như sau.

Pipeline 1: từ ảnh ra "object embedding"

Pipeline xử lý ảnh: từ Image qua Backbone, PAN, ba mức feature P3/P4/P5, rồi qua ba head Segmentation/Regression/Embedding

Phần này cùng cấu trúc với YOLOv8 và YOLO11 vốn đã được tối ưu khắt khe cho tốc độ. Có ba bước:

1. Backbone. Một CNN sâu, nhiệm vụ duy nhất là biến ảnh thô thành feature map. Bạn hãy hình dung backbone như một bộ máy nén ảnh: nó càng đi sâu, ảnh càng nhỏ về không gian (height × width) nhưng càng nhiều "kênh" thông tin trừu tượng (đường nét, hình thù, texture, chứ không còn là pixel màu). Trong YOLOv8, backbone là một biến thể CSPDarknet.

2. PAN (Path Aggregation Network). Đây là phần đáng dừng lại 30 giây vì nhiều người không hiểu PAN làm gì.

Vấn đề mà PAN giải: trong một ảnh có thể có cả con kiến nhỏ xíu và cái xe tải khổng lồ. Nếu bạn chỉ dùng feature map ở đoạn cuối backbone (đã downsampled rất nhiều lần), thì con kiến biến mất luôn vì nó nhỏ quá. Nếu bạn chỉ dùng feature map ở đoạn đầu backbone (còn high-resolution), thì cái xe tải lại "không lọt vào khung nhìn" của một filter đơn lẻ vì filter chỉ nhìn được vùng nhỏ.

PAN giải bằng cách giữ lại nhiều mức feature ở các độ phân giải khác nhau, rồi trộn thông tin giữa các mức theo cả hai chiều: từ low-res xuống high-res (để mức high-res biết "ngữ cảnh tổng thể"), và từ high-res lên low-res (để mức low-res biết "chi tiết bị mất"). Output cuối là ba feature map ở ba mức:

  • $P_3$: feature map độ phân giải cao, dành cho object nhỏ.
  • $P_4$: feature map độ phân giải trung bình.
  • $P_5$: feature map độ phân giải thấp, dành cho object to.

Một cách hình dung đơn giản: PAN giống như bạn xem một bức ảnh bằng ba mức zoom khác nhau, vừa zoom kỹ (P3) để thấy chi tiết nhỏ, vừa zoom rộng (P5) để thấy bố cục chung, và đảm bảo ba mức "trao đổi thông tin" với nhau. Ba mức này gọi là multi-scale features.

PAN trao đổi feature giữa các mức zoom khác nhau, để model thấy được cả object nhỏ và object to

3. Ba head song song. Mỗi điểm trên feature map (gọi là "anchor point", có $N$ điểm tổng cộng) được đưa qua ba head:

  • Regression head: dự đoán bounding box (4 số: x, y, width, height).
  • Segmentation head: dự đoán prototype mask để vẽ pixel-level shape.
  • Object embedding head: thay vì dự đoán class index như YOLO truyền thống, head này nhả ra một vector $D$ chiều (thường $D$ trong khoảng 256 đến 512). Vector này encode "object trông như thế nào về mặt feature", chưa gắn với tên class nào.

Toàn bộ output của object embedding head là ma trận $\mathcal{O} \in \mathbb{R}^{N \times D}$, với mỗi hàng là một vector cho một anchor point. Đây là sản phẩm chính của pipeline ảnh.

Pipeline 2: từ prompt ra "prompt embedding"

Phần này tách hẳn ra khỏi pipeline ảnh và là chỗ YOLOE khác YOLO truyền thống. Tùy chế độ prompt, có ba nhánh khác nhau, nhưng đầu ra đều cùng dạng: một ma trận prompt embedding $\mathcal{P} \in \mathbb{R}^{C \times D}$ với $C$ là số category đang quan tâm (ví dụ: 80 cho COCO, 1203 cho LVIS).

Ba nhánh prompt cùng đổ về một ma trận P duy nhất: text qua RepRTA, ảnh mẫu qua SAVPE, không prompt qua LRPC
  • Text prompt (gõ tên category dạng chữ, ví dụ "person, dog, cat"): đi qua module RepRTA (Re-parameterizable Region-Text Alignment).
  • Visual prompt (đưa ảnh mẫu kèm bounding box quanh object cần tìm): đi qua module SAVPE (Semantic-Activated Visual Prompt Encoder).
  • Prompt-free (không cần gì cả, model tự tìm tự đặt tên): đi qua module LRPC (Lazy Region-Prompt Contrast).

Bất kể bạn dùng nhánh nào, output đều là cùng một loại vector $\mathcal{P}$, plug được vào điểm gặp ở dưới. Đây là lý do YOLOE gọi là "unified": ba chế độ chia sẻ chung một backbone, một head, một phép so khớp, chỉ khác nhau ở module tạo ra $\mathcal{P}$.

Mỗi nhánh có một trick toán riêng để giữ chi phí inference thấp. Mình sẽ bóc tách lần lượt từng cái ở ba section sau.

Điểm gặp: tích vô hướng

Đây là chỗ hai pipeline gặp nhau, và là khâu cuối cùng để ra label.

Bạn có $\mathcal{O} \in \mathbb{R}^{N \times D}$ ($N$ vector từ ảnh) và $\mathcal{P} \in \mathbb{R}^{C \times D}$ ($C$ vector từ prompt). Để biết anchor point nào "thuộc về" category nào, bạn nhân ma trận:

$$ \text{Label} = \mathcal{O} \cdot \mathcal{P}^T : \mathbb{R}^{N \times D} \times \mathbb{R}^{D \times C} \rightarrow \mathbb{R}^{N \times C} $$

Output là một ma trận $N \times C$, mỗi ô (i, j) là score "anchor i có khả năng là category j không". Nếu cả hai vector đã được normalize, đây chính là cosine similarity, tức là phép so giống của face recognition. Anchor nào có score cao với category nào thì gán nhãn đó.

Đây chính là tinh thần của face recognition mà mình đã thắc mắc từ 2024. YOLOE dịch hệt cấu trúc đó sang object detection. Phần khó còn lại là làm sao tạo ra prompt embedding $\mathcal{P}$ một cách rẻ và chính xác cho cả ba chế độ. Đó là chỗ ba trick xuất hiện.

Trick 1: RepRTA (Re-parameterizable Region-Text Alignment), được lợi mà không phải trả phí gì

Bài toán nhỏ trước. Bạn có $C$ category names dạng text, ví dụ "person, dog, cat, ...". Để đưa vào công thức trên, bạn cần biến chúng thành ma trận $\mathcal{P} \in \mathbb{R}^{C \times D}$ sao cho aligned tốt với object embeddings của model.

Cách đơn giản nhất là chạy CLIP text encoder lên các category name để được embedding, rồi nhét thẳng vào. Cách này đã được dùng trong nhiều paper, nhưng có hai vấn đề. Một, CLIP text embedding học từ image-text pair cấp ảnh, không nhất thiết khớp với object-level features của detector. Hai, nếu bạn fuse text và image bằng cross-modality attention để cải thiện alignment (như YOLO-World), thì vocab càng to inference càng chậm. Với 1203 class của LVIS, bạn nhân thêm một mớ phép tính ở mỗi forward pass.

YOLOE giải bằng hai bước, và bước thứ hai là chỗ mình thấy đẹp nhất.

Bước 1: cache trước, tính sau. Trước khi training, bạn chạy CLIP text encoder lên toàn bộ category name có trong dataset, lưu các embedding ra đĩa. Trong suốt quá trình training, không cần load text encoder vào VRAM, chỉ cần load file cache. Đây không phải sáng tạo gì ghê gớm, nhưng giảm đáng kể bộ nhớ và thời gian mỗi step.

Bước 2: auxiliary network có thể "gập" được. Để tăng alignment, YOLOE thêm một mạng phụ $f_\theta$ rất nhỏ, chỉ một SwiGLU FFN block. Trong training, embedding text $P$ được biến đổi thành $f_\theta(P)$ trước khi đi vào tích vô hướng với object embedding. Mạng phụ này có vài chục nghìn tham số, training cost gần như không đổi.

RepRTA và SAVPE structure

Đến đây nếu dừng lại thì YOLOE chỉ là YOLO-World với một mạng phụ. Nhưng paper đi tiếp một bước.

Ý tưởng: lúc inference, $P$ là cố định (vì category names cố định cho một lần dùng), nên $f_\theta(P)$ cũng cố định. Object embedding head cuối cùng là một lớp convolution $1 \times 1$ với kernel $K \in \mathbb{R}^{D \times D' \times 1 \times 1}$. Kết quả cuối cùng là:

$$ \text{Label} = R(I \circledast K) \cdot (f_\theta(P))^T $$

trong đó $I$ là input feature, $\circledast$ là convolution. Vì convolution là tuyến tính và $f_\theta(P)$ cố định, bạn có thể "gộp" hai phép tính lại thành một kernel mới:

$$ K' = R_{C \times D \rightarrow C \times D \times 1 \times 1}(f_\theta(P)) \circledast K^T $$

Bây giờ inference chỉ là $\text{Label} = I \circledast K'$, hoàn toàn giống cấu trúc YOLO closed-set với $C$ output channels. Mạng phụ biến mất khỏi compute graph lúc inference.

Đây là kỹ thuật re-parameterization, có gốc từ RepVGG và MobileOne. Tinh thần là: trong training bạn dùng kiến trúc có nhiều branch để học tốt hơn, lúc inference bạn gập tất cả các branch lại thành một convolution duy nhất nhờ tính tuyến tính. Được cả hai đầu, mà không phải đánh đổi gì: model học được như mạng phức tạp, chạy nhanh như mạng đơn giản.

Một cách hình dung khác. Tưởng tượng bạn nấu ăn trong nhà hàng đông khách. Mạng phụ $f_\theta(P)$ giống như công đoạn sơ chế nguyên liệu. Cách tệ là khi khách gọi món, bạn vừa thái rau vừa nấu, làm cả hai cùng lúc cho mỗi đơn hàng. Cách tốt là sơ chế trước, đặt sẵn lên bàn, lúc khách gọi chỉ việc bốc và xào. Re-parameterization là cách thứ hai, ở dạng chính xác toán học.

Mise en place: nguyên liệu sơ chế sẵn trước giờ phục vụ, giống như cache textual embeddings và "gập" auxiliary network vào kernel trước khi inference

Kết quả thực tế: thêm RepRTA tăng AP từ 31.5 lên 33.5 (+2.0%) với zero overhead inference. Trong deep learning hiếm khi có cải thiện mà không kèm cái giá nào, RepRTA là một trong những trường hợp hiếm đó.

Trick 2: SAVPE (Semantic-Activated Visual Prompt Encoder), tách "object đó là cái gì" khỏi "nó nằm ở đâu"

Đây là phần đáp trực tiếp câu hỏi mà mình đã treo lơ lửng trong đầu hai năm. Với SAVPE, bạn không cần retrain mỗi khi có brand mới. Bạn chỉ cần đưa cho model một ảnh logo mẫu, model tự tạo prompt embedding, lưu vào database, và detect logo đó trong ảnh khác.

Bài toán: cho một ảnh mẫu kèm bounding box (hoặc mask) chỉ ra object cần tìm, hãy tạo ra một vector $\mathcal{P} \in \mathbb{R}^D$ encode đặc trưng của object đó. Sau này bất kỳ ảnh mới nào, bạn dùng $\mathcal{P}$ này contrast với object embedding của các anchor point để tìm những object tương tự.

Cách đơn giản nhất là mask pooling: lấy feature map của ảnh mẫu, masked-average-pool vùng trong bounding box, thế là xong. Cách này hoạt động được nhưng AP thấp hơn 1.5 điểm so với SAVPE. Tại sao?

Tại vì mask pooling đánh đồng tất cả các pixel trong bounding box với trọng số bằng nhau. Trong thực tế, không phải pixel nào cũng đại diện cho object như nhau. Pixel ở giữa logo Coca-Cola quan trọng hơn pixel sát mép box, vốn có thể là nền. Pixel rõ nét quan trọng hơn pixel mờ.

T-Rex2 giải vấn đề này bằng deformable attention transformer, để model tự học attention map. Đẹp về ý tưởng nhưng mỗi visual prompt phải qua transformer khá nặng. SAVPE giải bài toán giống vậy bằng kiến trúc rẻ hơn nhiều.

Ý chính: encode visual prompt thực ra cần làm hai việc khác nhau, và hai việc này có yêu cầu tính toán rất khác nhau.

  • Việc 1, "object đó trông như thế nào": cần feature giàu, $D$ kênh (256 đến 512 trong các YOLO scale khác nhau). Việc này không phụ thuộc vào prompt, chỉ phụ thuộc vào ảnh.
  • Việc 2, "vùng nào trong prompt đáng chú ý": cần một weight map tương đối nhỏ. Việc này phụ thuộc vào prompt mask.

SAVPE cấu trúc thành hai nhánh tách biệt theo đúng phân chia này.

Semantic branch xuất ra feature $S \in \mathbb{R}^{D \times H \times W}$ với $D$ kênh. Lấy $P_3, P_4, P_5$ từ PAN, mỗi mức qua hai conv $3 \times 3$, upsample lên cùng resolution, concat lại, project thành $D$ channels. Phần này không hề biết prompt là gì, nên có thể cache lại nếu bạn detect nhiều prompt khác nhau trên cùng một ảnh.

Activation branch nhận prompt mask, downsample, concat với image feature, ra output $\mathcal{W} \in \mathbb{R}^{A \times H \times W}$ với $A$ chỉ bằng 16 (paper default). Đây là weight map cho biết vùng nào trong prompt-indicated region đáng chú ý nhất. Vì $A$ rất nhỏ so với $D$, branch này nhẹ hơn semantic branch hàng chục lần.

Aggregation là chỗ kết hợp hai cái lại. Chia $D$ kênh của $S$ thành $A$ nhóm, mỗi nhóm $D/A$ kênh. Nhóm thứ $i$ dùng chung weight $\mathcal{W}_{i:i+1}$. Công thức:

$$ \mathcal{P} = \text{Concat}(G_1, ..., G_A); \quad G_i = \mathcal{W}{i:i+1} \cdot S^T $$

Nói nôm na, mỗi nhóm $D/A$ kênh chia sẻ một bản đồ trọng số, và được pool theo bản đồ đó để ra một phần của vector $\mathcal{P}$. Các nhóm khác nhau có thể chú ý các vùng khác nhau, vì có $A$ weight maps khác nhau.

Một cách hình dung. Tưởng tượng bạn là designer thiết kế poster cho 16 thương hiệu khác nhau dùng chung một bộ ảnh nền. Cách tệ là vẽ lại nội dung 16 lần với 16 layout khác nhau. Cách tốt là vẽ nội dung chi tiết một lần (semantic branch, nặng), rồi làm 16 tấm overlay highlight chỉ ra vùng nào nổi bật cho mỗi thương hiệu (activation branch, nhẹ). Output cuối là phép kết hợp giữa hai layer, không phải vẽ từ đầu.

Một poster nền chi tiết, nhiều layer overlay nhẹ chỉ ra vùng focus khác nhau, giống như semantic branch dùng chung và activation branch nhỏ riêng cho từng prompt

Số liệu: SAVPE so với mask pooling thuần tăng AP từ 30.4 lên 31.9 (+1.5). Khi $A=1$ thì AP là 30.9, $A=16$ là 31.9, $A=32$ vẫn 31.9. Tức là 16 nhóm là sweet spot. So với T-Rex2, YOLOE-v8-L có AP_r cao hơn 3.3 điểm với 2 lần ít data hơn và phần cứng nhẹ hơn (8 RTX4090 thay vì 16 A100).

Quay về bài toán logo của mình hồi 2024. Nếu mình build lại pipeline với YOLOE bây giờ:

  1. Khi onboard brand mới, chạy YOLOE encode visual prompt cho 1 đến 3 ảnh mẫu của logo đó. Lưu prompt embedding vào Postgres hoặc Redis. Không retrain.
  2. Khi process video mới, load tất cả prompt embedding từ database, đẩy qua YOLOE (forward pass duy nhất với batch nhiều prompt).
  3. Output là bounding box kèm tên brand cho mỗi object detect được.

Thời gian onboard một brand mới giảm từ 2-3 ngày training xuống còn vài giây encoding. Đây không phải tăng tốc 10%, mà thay đổi class của bài toán. Pipeline retrain biến mất, thay bằng pipeline embedding lookup.

Trick 3: LRPC (Lazy Region-Prompt Contrast), đôi khi reformulate bài toán còn quan trọng hơn modeling

Trick thứ ba dành cho chế độ prompt-free, tức là không cần text hay visual prompt nào cả, model tự tìm và tự gán tên cho mọi object trong ảnh. Đây là chế độ tổng quát nhất, và là phần mình thấy paper khôn nhất.

Cách làm phổ biến trước đó là treat đây như bài toán generation. GenerateU (NeurIPS 2023) gắn FlanT5-base 250 triệu tham số làm decoder, generate category name từng ký tự cho mỗi object đã detect. DINO-X dùng OPT-125M tương tự. Cả hai đều flexible, có thể đặt tên cho category mới chưa từng thấy trong vocabulary nào, nhưng cái giá là chậm khủng khiếp. GenerateU chỉ chạy 0.48 FPS trên T4, nghĩa là một frame mất hơn 2 giây. Realtime phải gọi là điều xa xỉ.

Câu hỏi của paper: thực ra chúng ta có cần generation không?

Trong 99% use case thực tế, các category name bạn muốn detect đều đã có sẵn trong tiếng Anh. Cốc, ghế, máy tính, xe đạp, cây, mèo. Bạn không cần model tự đặt tên mới cho cái cốc. Bạn cần model nhận ra cái cốc và gắn label "cup" có sẵn. Đây là bài toán retrieval, không phải generation.

LRPC (Lazy Region-Prompt Contrast) dịch insight này thành kiến trúc cụ thể. Có ba thành phần:

Một, specialized prompt embedding $\mathcal{P}_s$. Đây là một vector duy nhất $\mathcal{P}_s \in \mathbb{R}^D$, học trong một epoch riêng với data mà mọi class đều được gán nhãn cùng "object". Nhiệm vụ duy nhất của $\mathcal{P}_s$ là phân biệt anchor có object với anchor không có. Một dạng objectness vector học sẵn.

Hai, built-in vocabulary 4585 category names. Lấy từ Recognize Anything Model (RAM, ICCV 2023), một list tag cover phần lớn category thông dụng trong tiếng Anh. Mỗi tag được encode thành embedding bằng MobileCLIP, lưu sẵn.

Ba, lazy retrieval. Đây là phần "lazy" trong tên thuật toán. Cách naive là đem 4585 embeddings này dùng làm text prompt thông thường, contrast với mọi anchor. Như vậy bạn nhân ma trận $N \times D$ với $D \times 4585$ ở mọi anchor, kể cả những anchor không có object gì. Lãng phí khủng khiếp.

LRPC làm khôn hơn:

  1. Trước, dùng $\mathcal{P}_s$ filter ra tập anchor có khả năng chứa object: $\mathcal{O}' = {o \in \mathcal{O} \mid o \cdot \mathcal{P}_s^T > \delta}$, với $\delta$ là threshold (paper default $\delta = 0.001$).
  2. Chỉ với các anchor trong $\mathcal{O}'$ (thường là vài chục), mới retrieve category bằng cách contrast với 4585 embedding.
Module LRPC: anchor points đi qua specialized embedding để filter ra 𝒪' (chỉ vài anchor có object), rồi mới retrieve category từ built-in vocabulary

Đây là gần với pattern trong information retrieval cổ điển: lọc thô trước (cheap filter), tinh sau (expensive ranking). Cùng kết quả, nhanh hơn rất nhiều. Số liệu: với YOLOE-v8-S, LRPC đạt 95.8 FPS so với 56.5 FPS của baseline naive (1.7x speedup), không drop AP. So với GenerateU chạy 0.48 FPS, đây là 53 lần nhanh hơn.

Một cách hình dung. Tưởng tượng bạn vào siêu thị tìm món ăn cho bữa tối. Cách generation giống như bạn gọi đầu bếp riêng đến nhà, mô tả nguyên liệu trong tủ, đầu bếp tự sáng tạo món mới. Linh hoạt nhưng chậm và đắt. Cách retrieval giống như bạn nhìn vào menu siêu thị có sẵn, chọn món phù hợp. Ít linh hoạt hơn (giới hạn trong menu) nhưng nhanh và rẻ. Trong 99% trường hợp, menu đã đủ tốt.

Retrieval (chọn từ menu có sẵn, nhanh) so với Generation (đầu bếp tự sáng tạo từ đầu, chậm 50 lần)

Lazy retrieval là chỗ tinh tế. Bạn không tra cả menu cho mọi pixel. Bạn chỉ tra menu cho các pixel đã pass kiểm tra "có object hay không".

Phản biện: Nhưng generation flexible hơn chứ

Để công bằng với hướng generation, mình muốn trình bày phản biện ở dạng mạnh nhất.

Phản biện: GenerateU và DINO-X có thể đặt tên cho object không có trong vocabulary 4585 từ. Ví dụ object đó là một loại nhạc cụ truyền thống Việt Nam ít người biết, hay một sản phẩm kỹ thuật chuyên ngành. Với LRPC, bạn buộc retrieve trong 4585 tag cố định, không "anything goes".

Đây là phản biện đúng, và mình thừa nhận. Nhưng có ba điểm để pha loãng:

Một, 4585 categories đã cover phần lớn use case thông dụng. RAM tag list cover hầu hết object thường ngày trong ảnh internet. Nếu bài toán của bạn là "detect everything in this surveillance camera frame" thì 4585 từ là quá đủ.

Hai, nếu bạn cần domain-specific (medical instruments, retail SKUs, manufacturing parts), bạn có thể thay vocabulary. Đây là design quyết định, không phải bug. Nhét list 200 SKU của bạn vào, encode bằng MobileCLIP, dùng làm built-in vocab. Còn flexible hơn LLM hardcoded vì bạn control vocabulary ở cấp ứng dụng.

Ba, generation cũng hallucinate. Nó có thể đặt tên kỳ quặc cho object thông thường, vì decoder đôi khi đi lạc. Retrieval ít rủi ro hơn vì nó chỉ chọn từ một list có thật.

Thẳng thắn thừa nhận: nếu bạn cần "anything goes" cho category lạ chưa từng có trong vocab nào và sẵn sàng trả giá 100 lần chậm hơn, thì DINO-X hay GenerateU vẫn có chỗ. YOLOE chọn thiệt 5% flexibility để được 53 lần speedup. Đây là một cú đánh đổi mà phần lớn ứng dụng thực tế sẽ chấp nhận.

Pareto so với SOTA

Quan trọng phải nói rõ: YOLOE không phải state of the art về AP tuyệt đối. DINO-X to hơn vẫn đạt AP cao hơn. Nếu bạn chấm paper theo bảng leaderboard "AP cao nhất thắng", YOLOE thua.

Nhưng so theo Pareto frontier trên trục (AP, training cost, inference latency, deployability), YOLOE đứng vững ở vị trí mà chưa paper nào trước đó chiếm được.

Pareto frontier: SOTA ở góc trên trái (AP cao, FPS thấp), YOLOE đứng ở vị trí cân bằng giữa accuracy và speed

Vài con số đáng nhớ:

  • GLIP-T train 1337 giờ trên 8 V100. YOLOE-v8-L train 22.5 giờ trên 8 RTX4090. Khác biệt cấp số nhân về cost.
  • YOLOE-v8-S chạy 305 FPS trên T4, 64 FPS trên iPhone 12. Tức là model 12 triệu tham số chạy realtime trên thiết bị bạn đang cầm trong túi.
  • GenerateU 0.48 FPS, DINO-X 5.5 FPS. YOLOE prompt-free 25.3 FPS với AP cao hơn.

Trong industry research thường có sự nhầm lẫn giữa "SOTA on paper" và "best for production". Một model đạt thêm 2 AP nhưng cần 10 lần tài nguyên tính toán lúc inference thì không deploy được. YOLOE chọn vị trí ngược lại: chấp nhận ít AP hơn 1-2 điểm so với top-of-the-line, đổi lại deployable thật trên iPhone, Jetson Nano, hoặc browser WebAssembly.

Đây là bài học rộng hơn về cách đọc paper: đừng chỉ nhìn cột AP. Nhìn cả cột training time, inference FPS, parameter count, và (nếu có) memory peak. Một paper có AP thấp hơn 1 điểm nhưng training nhanh 4 lần và inference nhanh 1.4 lần thì là paper hay hơn về mặt practical.

Áp dụng YOLOE cho bài toán nhận diện logo (hoặc bất kỳ object mới nào)

Câu hỏi gốc của mình hai năm trước: tại sao object detection không làm được như face recognition?

Câu trả lời đầy đủ bây giờ là: làm được. YOLOE chứng minh điều đó. Nếu mình build lại pipeline logo bây giờ, lộ trình thực tế:

  1. Download YOLOE từ GitHub repo của THU-MIG, chọn YOLOE-v8-S (12M params, 305 FPS T4, 64 FPS iPhone) hoặc YOLOE-v8-L nếu cần AP cao hơn (45M params, 102 FPS T4, 27 FPS iPhone).
  2. Với mỗi brand, chuẩn bị 1 đến 3 ảnh logo mẫu kèm bounding box. Encode visual prompt embedding qua SAVPE. Lưu vào database.
  3. Khi process video mới, load tất cả prompt embedding, batch chạy YOLOE forward pass duy nhất. Output là bounding box kèm tên brand.
  4. Khi onboard brand mới, bước 2 chạy lại với ảnh logo mới. Không retrain model. Thời gian onboard giảm từ 2-3 ngày xuống vài giây.

Nếu bạn cũng đang vướng bài toán tương tự (logo, sản phẩm retail, defect manufacturing, bất kỳ thứ gì có "set object cần detect thay đổi liên tục"), đây có thể là cách tiếp cận đáng thử trước khi đụng vào việc retrain.

Tới đoạn này, mình muốn dừng và nghĩ rộng hơn một chút.

Ba trick của YOLOE đều không mới về kỹ thuật cá lẻ. Re-parameterization có từ RepVGG 2021. Decoupled architecture là pattern đã quen trong vision. Generation-to-retrieval là tinh thần của information retrieval cổ điển. Cái mới là góc nhìn tổng hợp: khi nào nên dùng technique nào.

Trong cơn sốt 2025-2026 với LLM và VLM 100B tham số, dễ có cảm giác mọi bài toán mới đều phải giải bằng mô hình to hơn, prompt khôn hơn. Nhưng YOLOE chứng minh điều ngược lại trong domain của nó: bạn có thể đạt 90% hiệu năng của mô hình to nhất với 1% tài nguyên tính toán, nếu bạn ngồi xuống và hỏi đúng câu hỏi về cách phát biểu bài toán.

Lần sau khi gặp một bài toán mới, mình sẽ phản xạ với cái gì trước? Tùy bài. Nhưng nhờ paper này, mình ít nhất sẽ dừng lại 5 phút để hỏi: có cách nào phát biểu lại bài toán để bỏ qua hẳn phần modeling không? Có kỹ thuật cũ nào mọi người đã quên không? Có chỗ nào trong pipeline có thể cache, gộp, hoặc lazy hóa không?

Đó là những câu hỏi mà nhiều khi mô hình to hơn không thay được.

Nếu bạn có gì muốn cãi lại, hoặc nghĩ mình đang nhầm chỗ nào, mình muốn nghe.

Bình.

Tài liệu tham khảo:

  • Paper gốc: Wang, Liu, Chen et al. YOLOE: Real-Time Seeing Anything. ICCV 2025. arXiv:2503.07465
  • Code và pretrained models: github.com/THU-MIG/yoloe
  • Cheng et al. YOLO-World: Real-Time Open-Vocabulary Object Detection. CVPR 2024. arXiv:2401.17270
  • Jiang et al. T-Rex2: Towards Generic Object Detection via Text-Visual Prompt Synergy. ECCV 2024. arXiv:2403.14610
  • Lin et al. Generative Region-Language Pretraining for Open-Ended Object Detection (GenerateU). CVPR 2024. arXiv:2403.10191
  • Ding et al. RepVGG: Making VGG-style ConvNets Great Again. CVPR 2021. arXiv:2101.03697
  • Huang et al. Recognize Anything: A Strong Image Tagging Model (RAM). ICCV 2023. arXiv:2306.03514