Netflix dạy LLM gợi ý phim để bạn xem mãi không dừng
- Published on
- /19 mins read/
Mười một giờ đêm, bạn tự hứa xem một tập thôi rồi đi ngủ. Hết tập, màn hình đếm ngược năm giây sang tập kế. Bạn bấm tắt, quay ra trang chủ, và ngay hàng đầu là một bộ phim tài liệu trúng gu tới mức hơi rợn. Một tiếng sau bạn vẫn còn thức.
Cái hàng phim "trúng gu tới mức hơi rợn" ấy là sản phẩm của một trong những hệ thống gợi ý được đầu tư nhiều nhất thế giới. Gần như mọi thứ bạn thấy trên Netflix, hàng nào hiện trước, phim nào nằm đầu hàng, thậm chí ảnh bìa nào hiện cho từng phim, đều được cá nhân hoá. Netflix từng nói phần lớn những gì người dùng xem đến từ gợi ý chứ không phải tìm kiếm.
Mấy năm gần đây Netflix chuyển dần hệ thống này sang một hướng rất giống cách người ta làm LLM. Mình thấy câu chuyện này hay không chỉ vì Netflix, mà vì nó cho thấy những ý tưởng từ LLM đang lan sang các bài toán tưởng chừng chẳng liên quan gì tới ngôn ngữ. Bài viết dựa trên những gì Netflix chia sẻ công khai qua tech blog và các bài báo khoa học của họ.
# một triệu đô và một bài học
Muốn hiểu vì sao Netflix đi hướng này, phải quay lại năm 2006. Netflix khi đó còn cho thuê đĩa DVD qua bưu điện, và họ mở cuộc thi Netflix Prize: đội nào cải thiện được độ chính xác dự đoán điểm đánh giá phim thêm 10% so với hệ thống của Netflix thì nhận một triệu đô.
Cuộc thi kéo dài gần ba năm, thu hút hàng nghìn đội trên khắp thế giới, và để lại cho ngành hai di sản lớn. Một là phổ biến kỹ thuật matrix factorization: biểu diễn mỗi người dùng và mỗi bộ phim bằng một vector, điểm đánh giá dự đoán chính là tích vô hướng của hai vector. Hai là bài học về ensemble: các đội dẫn đầu đều trộn rất nhiều model với nhau, có lời giải gộp tới hơn một trăm thuật toán.
Phần thú vị là chuyện sau đó. Trong bài viết năm 2012, Netflix kể rằng họ đưa vào production hai thuật toán nổi bật từ giai đoạn đầu của cuộc thi, nhưng không triển khai lời giải thắng giải cuối cùng, vì phần chính xác tăng thêm không đáng với công sức kỹ thuật để vận hành nó. Hơn nữa, khi Netflix chuyển sang streaming, bài toán đã đổi. Người ta không còn ngồi chấm điểm từng bộ phim nữa. Tín hiệu quan trọng trở thành hành vi: bấm xem, xem bao lâu, bỏ ngang ở phút thứ mấy, xem liền mấy tập.
Bài học mình rút ra từ giai đoạn này đến giờ vẫn đúng: model chính xác hơn trên giấy chưa chắc đáng triển khai, và bài toán bạn tối ưu hôm nay có thể không phải bài toán sản phẩm cần ngày mai.
# hệ thống gợi ý cổ điển trông thế nào
Ở quy mô hàng trăm triệu người và hàng chục nghìn bộ phim, không thể chấm điểm mọi bộ phim cho mọi người mỗi khi họ mở app. Nên hệ thống gợi ý hiện đại thường chạy theo phễu nhiều tầng.
Tầng đầu là retrieval (hay candidate generation): từ toàn bộ kho phim, lọc thật nhanh ra vài trăm ứng viên. Cách phổ biến là mô hình two-tower: một "tháp" biến người dùng thành vector, một tháp biến bộ phim thành vector, ứng viên là những phim có vector gần vector người dùng nhất. Vector phim tính sẵn, lúc chạy chỉ cần tính vector người dùng rồi tra cứu láng giềng gần, nhanh tới mức vài mili giây.
Tầng sau là ranking: chấm kỹ vài trăm ứng viên bằng một model nặng hơn, với hàng trăm, hàng nghìn feature. Rồi tới các bước sắp xếp trang: hàng nào lên trên, đảm bảo đa dạng để trang chủ không toàn phim cùng một thể loại, rồi chọn ảnh bìa cho từng phim.
Riêng chuyện ảnh bìa, Netflix từng viết từ năm 2017 rằng họ dùng contextual bandits để chọn ảnh bìa cho từng người: người hay xem phim tình cảm có thể thấy ảnh bìa có cảnh hai nhân vật chính, người hay xem phim hài thấy ảnh có diễn viên hài nổi tiếng trong cùng bộ phim đó.
Hệ thống kiểu này chạy tốt nhiều năm, nhưng có mấy vấn đề ngày càng lộ rõ.
Quá nhiều model chuyên biệt. Hàng "Tiếp tục xem", hàng "Vì bạn đã xem X", trang tìm kiếm, gợi ý sau khi xem xong... mỗi chỗ một model, một đội, một pipeline feature riêng. Netflix nêu đúng điểm đau này: tốn công duy trì, và kiến thức học được ở model này không dùng lại được cho model kia.
Feature thiết kế tay. Phần lớn sức mạnh nằm ở các feature do kỹ sư nghĩ ra: tỉ lệ xem hết, thể loại yêu thích trong 30 ngày, giờ hay xem... Thêm một tín hiệu mới đồng nghĩa với sửa pipeline ở nhiều nơi.
Các thiên lệch khó gỡ. Phim nằm ở vị trí đầu được bấm nhiều hơn chỉ vì nó nằm ở đầu (position bias). Hệ thống gợi ý phim A, người ta xem phim A, hệ thống học rằng phim A được thích, lại gợi ý phim A nhiều hơn (feedback loop). Phim phổ biến ngày càng phổ biến, phim ngách ngày càng chìm (popularity bias). Mấy thiên lệch này có trong mọi hệ thống gợi ý, và không có cách gỡ nào hoàn hảo.
# ý tưởng: lịch sử xem phim là một câu văn
Nhìn sang thế giới NLP, Netflix thấy một bài học. Trước LLM, mỗi bài toán ngôn ngữ có model riêng: dịch một model, tóm tắt một model, phân loại một model. Rồi người ta phát hiện chỉ cần train một model thật lớn trên thật nhiều văn bản với một nhiệm vụ duy nhất là đoán từ tiếp theo, model đó học được đủ thứ và tái sử dụng được cho mọi bài toán.
Áp vào gợi ý thì sao? Lịch sử của một người dùng cũng là một chuỗi có thứ tự. Mỗi lần xem, tìm kiếm, thêm vào danh sách, đánh giá là một "từ". Nhiệm vụ của model: đọc chuỗi đó và đoán "từ" tiếp theo, tức hành động tiếp theo của người dùng.
Câu văn: "Hôm nay trời đẹp nên tôi đi" -> đoán: "dạo"
Lịch sử xem: [Dark] [Mindhunter] [Ozark] [The Crown] -> đoán: tương tác tiếp theoÝ tưởng này không mới hoàn toàn. Giới nghiên cứu đã có những model gợi ý tuần tự dựa trên transformer từ khoảng 2018–2019, như SASRec (2018) hay BERT4Rec (2019). Cái mới ở Netflix là quy mô và tham vọng: không phải một model cho một tính năng, mà một foundation model dùng chung cho cả hệ thống.
Tháng 3/2025, Netflix công bố trên tech blog foundation model đó: một transformer lớn, học bán giám sát với mục tiêu đoán tương tác tiếp theo, trên lịch sử tương tác của hàng trăm triệu thành viên. Họ nói rõ là lấy cảm hứng từ hai bài học của LLM: ưu tiên dữ liệu lớn và chất lượng thay vì ngồi thiết kế feature, và tận dụng cách học "đoán phần tiếp theo" trên dữ liệu không cần gán nhãn.
# phim không phải là chữ
Nghe gọn gàng, nhưng làm thật thì đụng một loạt chỗ mà văn bản không có. Theo những gì Netflix chia sẻ, đây là mấy bài toán lớn.
"chữ" không ngang hàng
Trong văn bản, chữ nào cũng là một token như nhau. Trong lịch sử xem thì không. Xem trọn một bộ phim ba tiếng nói lên nhiều điều hơn hẳn việc lướt qua trailer năm giây. Một lần bấm nhầm rồi thoát ngay gần như là nhiễu.
Netflix mô tả việc biến lịch sử tương tác thành token khá giống bước tokenize trong NLP. Những tương tác liền nhau trên cùng một bộ phim được gộp lại thành một token, mang theo thông tin như thời lượng xem, thiết bị, thời điểm. Khi tính loss, những tương tác quan trọng hơn (ví dụ xem lâu) có thể được đánh trọng số cao hơn, để model không học cách đoán những cú bấm vô nghĩa.
câu văn quá dài
Một thành viên lâu năm có thể có hàng chục nghìn sự kiện, dài hơn nhiều so với context window hợp lý. Giống LLM, chi phí attention tăng theo bình phương độ dài chuỗi. Netflix đề cập việc dùng các kỹ thuật attention thưa và lấy mẫu những cửa sổ trượt trên lịch sử dài khi train, để model vẫn thấy được cả hành vi gần đây lẫn những thói quen từ lâu mà không phải xử lý toàn bộ một lúc.
đoán một bước là chưa đủ
LLM chỉ cần đoán đúng từ tiếp theo. Nhưng gợi ý phim cần hiểu gu dài hạn, không chỉ cú bấm kế tiếp. Ai vừa xem xong tập 3 của một series thì gần như chắc chắn sẽ xem tập 4, đoán đúng điều đó chẳng có gì đáng khen. Netflix nói tới việc cho model đoán nhiều bước phía trước thay vì chỉ một bước, và kết hợp thêm các mục tiêu phụ, để model học được xu hướng thay vì chỉ phản xạ.
từ mới mỗi tuần
Đây là chỗ khác LLM rõ nhất. Từ vựng của ngôn ngữ gần như cố định. Còn Netflix có phim mới mỗi tuần, và phim mới lại chính là thứ họ muốn gợi ý nhất. Một bộ phim vừa ra mắt chưa có ai xem, nên model chưa học được gì về "ID" của nó. Đây là bài toán cold start kinh điển.
Cách xử lý là không dựa hoàn toàn vào ID. Mỗi bộ phim được biểu diễn bằng cả vector học từ ID lẫn vector suy ra từ thông tin mô tả: thể loại, diễn viên, đạo diễn, nội dung, ngôn ngữ. Phim mới chưa có lịch sử thì dựa chủ yếu vào phần mô tả, càng có nhiều người xem thì phần học từ ID càng nặng ký. Giống như gặp một người mới: chưa quen thì đánh giá qua lý lịch, quen lâu rồi thì đánh giá qua những gì đã cùng trải qua.
kho từ vựng khổng lồ
Đoán "từ tiếp theo" nghĩa là tính xác suất cho mọi bộ phim trong kho ở mỗi bước. Với hàng chục nghìn bộ phim và hàng tỷ chuỗi train, tính softmax đầy đủ rất đắt. Kỹ thuật thường dùng trong gợi ý là sampled softmax: mỗi bước chỉ so đáp án đúng với một mẫu nhỏ các phim "sai" thay vì toàn bộ kho. Có điều phải lấy mẫu khéo, vì nếu toàn lấy phim phổ biến làm mẫu sai thì model lại học thêm một kiểu thiên lệch nữa.
Một điểm đáng chú ý: Netflix cho biết họ quan sát thấy model gợi ý của mình cũng tuân theo một dạng scaling law giống LLM. Model càng lớn, dữ liệu càng nhiều thì chất lượng tăng theo một quy luật khá đều. Đây là tín hiệu quan trọng, vì nó cho thấy đầu tư thêm phần cứng vào hướng này vẫn còn đem lại kết quả.
# đưa foundation model vào sản phẩm
Có model lớn rồi, làm sao để hàng chục tính năng khác nhau dùng được nó? Tháng 11/2025 Netflix chia sẻ tiếp về chuyện này, với ba cách tích hợp.
Cách nhẹ nhất là dùng embedding. Foundation model sinh ra vector cho mỗi người dùng và mỗi bộ phim, các model nhỏ ở từng tính năng chỉ việc lấy vector đó làm feature đầu vào. Không đụng vào model lớn, rủi ro thấp.
Cách thứ hai là ghép một phần của model vào model của từng tính năng, để phần đó được train tiếp cùng nhiệm vụ riêng.
Cách thứ ba là fine-tune cả model lớn cho một nhiệm vụ cụ thể, giống fine-tune một LLM nền. Mạnh nhất nhưng tốn kém nhất.
Có một vấn đề kỹ thuật nhỏ mà hay bị bỏ qua: mỗi lần train lại foundation model, vector của người dùng và phim có thể "xoay" sang một hệ toạ độ khác. Các model ở hạ nguồn đang dùng vector cũ sẽ bị lệch. Netflix kể họ train lại foundation model từ đầu mỗi tháng và fine-tune hằng ngày trên dữ liệu mới (bước này cũng thêm ID cho phim vừa ra mắt). Train lại từ đầu với khởi tạo ngẫu nhiên khác thì hai không gian embedding gần như chẳng liên quan gì nhau, nên họ thêm một bước ổn định embedding (embedding stabilization): ánh xạ embedding sinh ra mỗi ngày về cùng một hệ toạ độ, để các đội dùng không bị vỡ. Ai từng làm hệ thống phụ thuộc vào embedding đều sẽ gật gù ở chỗ này.
Bài học không riêng gì Netflix: khi có một model nền, đừng ép mọi đội dùng nó theo một kiểu. Cho họ một thang lựa chọn từ rẻ và đơn giản tới đắt và mạnh, và cam kết giữ giao diện ổn định như với một API.
# GenRec: một LLM thật sự xếp hạng phim
Foundation model ở trên học từ chuỗi ID, chưa phải một LLM hiểu ngôn ngữ. Tháng 8/2026, các nhà nghiên cứu của Netflix công bố paper về GenRec, đi thêm một bước: dùng một LLM thật làm bộ xếp hạng.
Theo paper, GenRec đi qua hai giai đoạn. Giai đoạn đầu lấy một LLM mã nguồn mở và cho nó làm quen với dữ liệu của Netflix. Giai đoạn hai chuyên biệt hoá model cho việc xếp hạng. Điểm khác biệt lớn là đầu vào không còn là hàng nghìn feature thiết kế tay, mà là lịch sử và ngữ cảnh người dùng được viết ra bằng lời. Có thể hình dung đại khái:
Người dùng vừa xem hết mùa 2 của một series tâm lý tội phạm, thường xem vào tối
cuối tuần trên TV, gần đây hay bỏ dở phim hài sau 10 phút, đã thêm 3 phim tài liệu
vào danh sách nhưng chưa xem. Hôm nay là tối thứ Sáu.
Ứng viên: [A] series trinh thám Bắc Âu, [B] phim hài lãng mạn, [C] phim tài liệu tội phạm.Với cách viết này, thêm một tín hiệu mới chỉ là thêm một câu, không phải sửa cả pipeline feature. Paper báo cáo rằng GenRec, dù dùng ít dữ liệu gán nhãn và ít tín hiệu đầu vào hơn hẳn, vẫn đạt cải thiện có ý nghĩa thống kê so với bộ xếp hạng đang chạy production, cả khi đo offline lẫn khi thử với người dùng thật.
Hai chi tiết kỹ thuật mình thấy đáng chú ý. Một, ở giai đoạn sau, model được tinh chỉnh bằng các tín hiệu phần thưởng bám theo yêu cầu kinh doanh và sự hài lòng lâu dài của thành viên, tức là một dạng RL giống cách các chatbot được dạy. Hai, để chạy nổi ở quy mô Netflix, họ dùng cách suy luận chỉ prefill: model đọc đầu vào một lượt rồi cho ra điểm số, không sinh từng chữ. Ai đọc bài chuyện gì xảy ra khi bạn nhấn Enter sẽ thấy đây là lựa chọn rất khôn: pha decode tuần tự là pha đắt và chậm nhất, mà xếp hạng thì chẳng cần sinh chữ nào.
# đo "hài lòng lâu dài" kiểu gì
Đến đây chắc có người hỏi: tóm lại Netflix dùng AI để giữ chân người xem càng lâu càng tốt?
Câu chuyện tinh tế hơn. Một hệ thống chỉ tối ưu cho cú click sẽ học đúng thứ mà cuộc đua clickbait đã dạy cả ngành: ảnh bìa giật gân, tiêu đề câu kéo, thứ khiến người ta bấm vào rồi xem năm phút thì thất vọng. Ngắn hạn số đẹp, dài hạn người ta huỷ gói. Với một dịch vụ thu phí theo tháng, thước đo quan trọng nhất là người dùng có còn thấy đáng tiền để ở lại tháng sau không.
Cái khó là "tháng sau" thì quá xa để làm tín hiệu train hay đo A/B test. Không ai chờ nổi vài tháng để biết một thay đổi nhỏ trên trang chủ có tốt không. Nên các đội gợi ý phải tìm chỉ số trung gian (proxy metric): những thứ đo được trong vài ngày nhưng có tương quan với việc người dùng ở lại lâu dài. Netflix có nhiều nghiên cứu về chuyện chọn và kiểm chứng những chỉ số trung gian như vậy, và đây là phần mình thấy khó nhất, ít được nói tới nhất trong cả câu chuyện. Chỉ số trung gian chọn sai thì model càng được tối ưu kỹ, sản phẩm càng lệch khỏi thứ người dùng thật sự cần.
Dù vậy, ranh giới giữa "gợi ý đúng thứ bạn thích" và "khiến bạn không dừng lại được" không phải lúc nào cũng rõ. Nút "tập tiếp theo" tự chạy, trailer tự phát, đều là lựa chọn thiết kế sản phẩm chứ không chỉ là thuật toán. Chuyện này làm mình nhớ tới bài vì sao LLM cứ đồng ý với bạn: hệ thống nào tối ưu cho phản hồi ngắn hạn cũng dần chiều theo điều người dùng muốn trong khoảnh khắc, mà điều đó chưa chắc tốt cho họ.
# một team nhỏ học được gì
Bạn không cần hàng trăm triệu người dùng để áp dụng những ý tưởng này.
Ý tưởng coi hành vi người dùng là chuỗi dùng được cho rất nhiều sản phẩm: chuỗi sản phẩm khách xem trong shop online, chuỗi bài học trong app học, chuỗi bài viết trên blog. Bước đầu tiên luôn là biến log sự kiện thành chuỗi token sạch sẽ, gộp những sự kiện vụn vặt, gắn trọng số:
from itertools import groupby
events = [ # (user, thời điểm, hành động, nội dung, số giây)
("u1", "2026-10-01T20:00", "play", "dark_s1e1", 3200),
("u1", "2026-10-01T20:55", "play", "dark_s1e1", 400), # xem tiếp cùng tập
("u1", "2026-10-01T21:05", "trailer", "ozark", 30),
("u1", "2026-10-02T21:00", "play", "ozark_s1e1", 3500),
]
def to_tokens(user_events):
tokens = []
# gộp các sự kiện liên tiếp trên cùng một nội dung, cộng dồn thời gian
for (action, item), group in groupby(user_events, key=lambda e: (e[2], e[3])):
seconds = sum(e[4] for e in group)
weight = min(seconds / 3600, 1.0) # xem càng lâu càng nặng ký, tối đa 1
if weight >= 0.05: # bỏ tương tác quá hời hợt
tokens.append((item, round(weight, 2)))
return tokens
print(to_tokens(events))
# [('dark_s1e1', 1.0), ('ozark_s1e1', 0.97)]Có chuỗi rồi thì một model đoán phần tử tiếp theo kiểu SASRec không lớn như bạn nghĩ. Đây là phần lõi viết bằng PyTorch, đủ để train trên một máy có GPU bình thường với vài trăm nghìn người dùng:
import torch
import torch.nn as nn
class NextItemModel(nn.Module):
def __init__(self, num_items: int, dim: int = 128, max_len: int = 200, layers: int = 2, heads: int = 4):
super().__init__()
self.item_emb = nn.Embedding(num_items + 1, dim, padding_idx=0) # 0 là padding
self.pos_emb = nn.Embedding(max_len, dim)
block = nn.TransformerEncoderLayer(dim, heads, dim * 4, dropout=0.1, batch_first=True)
self.encoder = nn.TransformerEncoder(block, layers)
def forward(self, seq: torch.Tensor) -> torch.Tensor:
# seq: [batch, len] chứa ID nội dung, đệm 0 ở cuối chuỗi (bên phải). Đừng đệm bên trái:
# vị trí đệm đầu chuỗi bị causal mask che hết key, lúc suy luận (eval + no_grad) attention
# ra NaN và lan sang cả các vị trí thật
positions = torch.arange(seq.size(1), device=seq.device)
x = self.item_emb(seq) + self.pos_emb(positions)
# causal mask: mỗi vị trí chỉ nhìn các tương tác trước nó, giống LLM
mask = nn.Transformer.generate_square_subsequent_mask(seq.size(1)).to(seq.device)
h = self.encoder(x, mask=mask, src_key_padding_mask=seq.eq(0))
# điểm cho mọi nội dung = tích vô hướng với embedding của nội dung (dùng chung trọng số)
return h @ self.item_emb.weight.T # [batch, len, num_items + 1]
def loss_fn(logits: torch.Tensor, targets: torch.Tensor, weights: torch.Tensor) -> torch.Tensor:
# targets: nội dung thật ở bước kế tiếp; weights: trọng số theo thời gian xem (0 ở chỗ padding)
ce = nn.functional.cross_entropy(logits.flatten(0, 1), targets.flatten(), reduction="none")
return (ce * weights.flatten()).sum() / weights.sum().clamp(min=1)Đối chiếu với những gì Netflix mô tả, bạn sẽ thấy các mảnh ghép quen thuộc: causal mask như LLM, embedding cho từng nội dung, loss có trọng số theo mức độ quan tâm. Muốn thêm cold start thì cộng thêm một vector suy ra từ thông tin mô tả vào item_emb. Kho nội dung lớn tới vài trăm nghìn mục thì đổi cross entropy đầy đủ sang sampled softmax.
Ý tưởng viết ngữ cảnh bằng lời để LLM xếp hạng cũng dễ thử: lấy vài chục ứng viên từ hệ thống hiện có, viết lịch sử người dùng thành vài câu, nhờ LLM sắp xếp lại. Nhưng đo cẩn thận bằng A/B test trước khi tin, và để ý chi phí, vì mỗi lần xếp hạng giờ là một lần gọi model.
Còn quan trọng nhất vẫn là chọn đúng thứ cần tối ưu. Click, thời gian xem, hay người dùng có quay lại sau một tháng không? Mỗi lựa chọn dẫn tới một sản phẩm rất khác, và chọn sai thì model càng giỏi càng hại.
Muốn đọc sâu thêm, tech blog của Netflix và trang Netflix Research là hai nơi nên theo dõi. Bài cuối của loạt này mình chuyển sang chủ đề thực dụng hơn cho dân code hằng ngày: những tính năng của Claude Code mà nhiều người dùng hằng ngày vẫn chưa biết.
Tài liệu tham khảo:
- Netflix Technology Blog: Foundation Model for Personalized Recommendation (3/2025)
- Netflix Research: Integrating Netflix's Foundation Model into Personalization Applications (11/2025)
- GenRec: An LLM-Backed Recommendation Ranker at Netflix (arXiv, 2026)
- Netflix Technology Blog: Netflix Recommendations, Beyond the 5 stars (2012)
- Netflix Technology Blog: Artwork Personalization at Netflix (2017)
- Self-Attentive Sequential Recommendation, SASRec (Kang và McAuley, 2018)
- Matrix Factorization Techniques for Recommender Systems (Koren, Bell, Volinsky, 2009)
Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!
Happy coding 😎 👍🏻 🚀 🔥.
On this page
- # một triệu đô và một bài học
- # hệ thống gợi ý cổ điển trông thế nào
- # ý tưởng: lịch sử xem phim là một câu văn
- # phim không phải là chữ
- "chữ" không ngang hàng
- câu văn quá dài
- đoán một bước là chưa đủ
- từ mới mỗi tuần
- kho từ vựng khổng lồ
- # đưa foundation model vào sản phẩm
- # GenRec: một LLM thật sự xếp hạng phim
- # đo "hài lòng lâu dài" kiểu gì
- # một team nhỏ học được gì