Xây Dựng Công Cụ Tìm Kiếm Web với 3 Tỷ Neural Embeddings Trong 2 Tháng

Vào một ngày, tôi quyết định thử thách bản thân bằng một dự án đầy tham vọng: xây dựng công cụ tìm kiếm web từ con số 0. Ngoài việc đào sâu kiến thức, có hai lý do chính thúc đẩy tôi:

  • Chất lượng kết quả tìm kiếm đang giảm sút, với nhiều nội dung spam SEO và ít thông tin giá trị.
  • Sự bùng nổ của các mô hình transformer-based text embedding mang lại khả năng hiểu ngôn ngữ tự nhiên vượt trội.

Một câu hỏi đơn giản luôn ám ảnh tôi: Tại sao công cụ tìm kiếm không thể luôn trả về những nội dung chất lượng cao nhất? Những nội dung này có thể hiếm, nhưng với kho tàng thông tin khổng lồ trên Internet, chúng xứng đáng được xếp hạng cao hơn những thứ vô bổ tràn lan hiện nay.

Những Thách Thức Khi Xây Dựng Công Cụ Tìm Kiếm

Các công cụ tìm kiếm hiện tại thường chỉ dừng lại ở mức khớp từ khóa đơn giản, thay vì thực sự thông minh như con người. Những truy vấn phức tạp hoặc tinh tế thường không có câu trả lời chính xác, trong khi tiềm năng giải quyết chúng là rất lớn.

Xây dựng công cụ tìm kiếm đòi hỏi kiến thức đa ngành: khoa học máy tính, ngôn ngữ học, bản thể học, xử lý ngôn ngữ tự nhiên, học máy, hệ thống phân tán, tối ưu hiệu suất… Tôi muốn xem mình có thể học và áp dụng bao nhiêu trong thời gian ngắn. Và tất nhiên, sở hữu một công cụ tìm kiếm riêng cũng là điều cực kỳ thú vị.

Hành Trình 2 Tháng Đầy Thử Thách

Trong bài viết này, tôi sẽ kể lại hành trình từ lúc bắt đầu với:

  • Không có cơ sở hạ tầng
  • Không dữ liệu ban đầu
  • Không kinh nghiệm xây dựng công cụ tìm kiếm

Một số thành tựu nổi bật:

  • 200 GPU tạo ra tổng cộng 3 tỷ SBERT embeddings
  • Hàng trăm crawler thu thập 50.000 trang mỗi giây, xây dựng chỉ mục 280 triệu trang
  • Độ trễ truy vấn cuối cùng khoảng 500ms
  • RocksDB và HNSW được phân mảnh trên 200 lõi, 4TB RAM và 82TB SSD

Bạn có thể trải nghiệm bản demo trực tiếp của công cụ tìm kiếm này. Dưới đây là sơ đồ kiến trúc tổng quan của hệ thống:

Kiến Trúc Hệ Thống

  • Môi trường thử nghiệm
  • Chuẩn hóa dữ liệu
  • Phân đoạn nội dung
  • Ngữ cảnh ngữ nghĩa
  • Chuỗi phát biểu
  • Kết quả ban đầu
  • Bộ thu thập dữ liệu
  • Quy trình xử lý
  • Lưu trữ
  • Service mesh
  • Triển khai GPU
  • HNSW phân mảnh
  • Tối ưu độ trễ
  • Đồ thị tri thức
  • Trang kết quả tìm kiếm
  • Trợ lý AI
  • Theo dõi trạng thái
  • Chất lượng tìm kiếm
  • Bản demo trực tiếp
  • Chi phí
  • Kết luận và hướng phát triển

Môi Trường Thử Nghiệm

Tôi bắt đầu bằng việc tạo một môi trường thử nghiệm nhỏ để kiểm tra hiệu quả của neural embeddings trong tìm kiếm: lấy một trang web, phân đoạn nó và xem liệu tôi có thể trả lời chính xác các truy vấn ngôn ngữ tự nhiên phức tạp hay không.

Ví dụ với tài liệu S3:

Truy vấn Tìm kiếm truyền thống Tìm kiếm neural
Tôi muốn dùng S3 thay cho Postgres nhưng với cơ sở dữ liệu có thể gắn nhận xét của con người với tệp trong cột khác Kết quả ngẫu nhiên về Postgres, S3, tệp Bạn cũng có thể chỉ định metadata tùy chỉnh khi lưu trữ đối tượng.
Tại sao CORS vẫn không hoạt động sau khi cho phép tất cả? Đoạn trích ngẫu nhiên về CORS, “S3 không hoạt động”, quyền đối tượng Cấu hình bucket có mô hình nhất quán cuối cùng…

Về cơ bản, công cụ tìm kiếm cần hiểu ý định, không chỉ từ khóa:

  • Hiểu toàn bộ truy vấn thay vì tách thành từ khóa riêng lẻ
  • Không cần kỹ thuật truy vấn phức tạp: toán tử, định dạng, từ ngữ chính xác
  • Ánh xạ chính xác các truy vấn “đầu lưỡi”, ngầm định và khái niệm
  • Trả lời các truy vấn đa khái niệm, phức tạp và tìm ra mối quan hệ không rõ ràng

Xử Lý Dữ Liệu

HTML biểu diễn nội dung thông qua các thẻ với nhiều mục đích khác nhau: bố cục, văn bản, phương tiện, tương tác, metadata và lập trình ứng dụng. Vì công cụ tìm kiếm tập trung vào nội dung văn bản, bước đầu tiên là làm sạch và chuẩn hóa markup nhiễu từ trang thu thập được, sau đó trích xuất văn bản ngữ nghĩa.

Mục tiêu là loại bỏ tất cả các yếu tố không phải nội dung chính như:

  • Thanh menu, liên kết điều hướng, banner, chân trang
  • Phần bình luận, nội dung phụ, liên kết bài viết khác
  • Yếu tố giao diện, biểu mẫu, nút điều khiển

Phân Đoạn Nội Dung

Sau khi có văn bản sạch, bước tiếp theo là phân đoạn. Hầu hết các mô hình embedding không thể xử lý đầu vào cả trang và thường mất khả năng biểu diễn ở độ dài đó. Embedding ở cấp độ trang cũng quá thô, không hữu ích để xác định chính xác.

Cách tiếp cận phổ biến là chia mỗi n ký tự hoặc từ. Nhưng điều này có thể cắt đứt từ ngữ, ngữ pháp và cấu trúc làm hỏng ý nghĩa. Tôi chọn chia thành câu – ranh giới tự nhiên và mạch lạc – bằng mô hình sentencizer được huấn luyện.

Ngữ Cảnh Ngữ Nghĩa

Vấn đề lớn với phân đoạn là ngữ cảnh. Một câu xây dựng dựa trên các câu trước, đoạn văn chứa nó, phần hiện tại đang thảo luận… Các tham chiếu gián tiếp (“nó”, “cái này”, “sau đó”…) mất ý nghĩa nếu đoạn bị tách khỏi ngữ cảnh.

Bước đầu tiên là tận dụng cây tài liệu ngữ nghĩa đã chuẩn hóa. Ví dụ:

  • Tiêu đề chỉ định phần lồng nhau hoặc phân chia
  • Tiêu đề bảng gắn nhãn cho các ô trong mỗi hàng
  • Câu dẫn trước một danh sách giải thích ý nghĩa của danh sách đó

Kết Quả Ban Đầu

Tôi xây dựng giao diện người dùng để trực quan hóa và tương tác với các trang trong môi trường thử nghiệm. Kết quả khá tốt.

Ví dụ trên trang tài liệu S3, một câu hỏi ngôn ngữ tự nhiên đưa ra nhiều câu trả lời trực tiếp từ các đoạn trích khác nhau không liên quan trực tiếp đến truy vấn.

Một ví dụ khác, truy vấn trang web này, công cụ tìm kiếm khớp với “It’s not worth it” – câu trả lời trực tiếp và phù hợp nhất, nhưng không có ngữ cảnh sẽ không có ý nghĩa và không được khớp.

Bộ Thu Thập Dữ Liệu

Tôi chuyển sang xây dựng crawler Node.js với các yêu cầu:

  • Phân phối tác vụ thông qua work stealing
  • Kiểm soát và xác minh DNS, URL, chuyển hướng, tiêu đề và bộ đếm thời gian
  • Phân phối tác vụ trên nhiều crawler và xử lý giới hạn tốc độ
  • Quản lý nghiêm ngặt tài nguyên để tránh rò rỉ bộ nhớ

Lưu Trữ

Ban đầu, tôi chọn Oracle Cloud do chi phí egress thấp với 10TB miễn phí mỗi tháng. Dịch vụ lưu trữ đối tượng của họ là nơi lưu trữ các trang thô và dữ liệu phái sinh, nhưng nhanh chóng gặp vấn đề về tỷ lệ ghi kích thước lớn.

Một thời gian sau, tôi lưu trữ blob trong PostgreSQL cùng với dữ liệu hàng. PostgreSQL có cơ chế TOAST giúp giảm bớt vấn đề bằng cách chia nhỏ và lưu trữ các blob lớn trong bảng riêng.

Cuối cùng, tôi chuyển sang RocksDB cho cả ghi chép/metadata và blob. RocksDB giải quyết nhiều hạn chế:

  • Ghi tuần tự vào WAL và giữ sắp xếp trong bộ nhớ
  • Đường dẫn chèn đơn giản và nhanh, bỏ qua nhiều cơ chế RDBMS
  • Gọi hàm thư viện nhúng trực tiếp thay vì giao thức dây và kết nối

Kết Luận Và Hướng Phát Triển

Một trong những giá trị lớn nhất của neural embeddings là khả năng tìm kiếm nội dung, thông tin chi tiết và tài liệu tham khảo chất lượng. Những thứ này thường nằm trong các bài luận và tài liệu. Các truy vấn chỉ là “đánh dấu trang” hoặc khớp cụm từ chính xác yêu cầu chỉ mục rất rộng nhưng không tận dụng trí thông minh hoặc sự hiểu biết.

Embeddings dường như mạnh mẽ hơn nhiều so với tìm kiếm truyền thống. Với các công cụ tìm kiếm điển hình, càng cụ thể, kết quả càng tệ. Khả năng thu hẹp và phóng to thông qua một truy vấn rất cụ thể để tìm chính xác những gì bạn muốn thông qua những hiểu biết và mối quan hệ ẩn là rất mạnh mẽ.

Mặc dù có sự trỗi dậy của LLM, tôi nghĩ rằng tìm kiếm sẽ luôn đóng một vai trò: LLM không thể và không nên ghi nhớ tất cả kiến thức. Thay vào đó, LLM có thể chuyển tải kiến thức đó thông qua các chỉ mục dày đặc hiệu quả này, đồng nghĩa với việc ít ảo giác hơn và thông tin cập nhật hơn.

Chỉ mục