Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 32 — AI Spec: Latency Budget

Viết PRD & Product Spec cho PM Bài 32/60

Mở đầu — vì sao bài này quan trọng

Có một sự thật phũ phàng mà nhiều PM bỏ qua khi viết spec cho tính năng AI: người dùng không quan tâm model của bạn thông minh đến đâu nếu họ phải chờ quá lâu để thấy kết quả. Một câu trả lời chính xác 95% nhưng mất 12 giây để hiển thị thường thua một câu trả lời chính xác 88% nhưng hiện ra trong 2 giây. Trải nghiệm chờ đợi giết chết sản phẩm nhanh hơn cả việc trả lời sai.

Khi bạn viết spec cho một tính năng "thường" — ví dụ một nút bấm, một form — độ trễ (latency) hầu như không phải bận tâm vì backend phản hồi trong vài chục mili-giây. Nhưng tính năng AI/LLM thì khác hẳn về bản chất. Một lệnh gọi tới model ngôn ngữ lớn có thể mất từ 800ms tới 8 giây tùy độ dài output, tùy model, tùy việc bạn có gọi retrieval (RAG) hay không, tùy tải hệ thống lúc đó. Latency không còn là chi tiết kỹ thuật — nó trở thành một quyết định sản phẩm mà bạn, với tư cách PM, phải đặt ra rõ ràng ngay trong spec.

Đó là lý do bài này tồn tại. "Latency Budget" — ngân sách độ trễ — là cách bạn cam kết bằng con số: tính năng này được phép chậm tối đa bao nhiêu, và ngân sách đó được chia ra sao cho từng bước trong pipeline. Nếu bạn không viết nó ra, kỹ sư sẽ tối ưu mò mẫm, QA không biết khi nào nên fail một bản build, và đến lúc launch bạn mới phát hiện p95 là 9 giây — quá muộn để sửa. Bài này dạy bạn cách biến "phải nhanh" mơ hồ thành một bảng ngân sách cụ thể mà cả team đọc là làm được.

Khái niệm cốt lõi

Latency budget là gì

Latency budget là tổng thời gian tối đa mà bạn cho phép từ lúc người dùng kích hoạt hành động đến lúc họ nhận được kết quả có ý nghĩa, được chia nhỏ (breakdown) thành ngân sách con cho từng bước trong pipeline xử lý. Nó giống như cách bạn lập ngân sách chi tiêu: tổng quỹ là 3 giây, vậy network được tiêu bao nhiêu, retrieval bao nhiêu, model bao nhiêu, render bao nhiêu — cộng lại không được vượt quỹ.

Điểm mấu chốt: latency budget không phải một con số đơn lẻ mà là một bảng phân rã. Lý do là khi có sự cố chậm, bạn cần biết "tiền đi đâu" — bước nào đang ngốn thời gian — để biết phải tối ưu chỗ nào.

Đo bằng percentile, không bao giờ đo bằng trung bình

Đây là sai lầm số một. Nếu spec của bạn viết "latency trung bình dưới 2 giây", bạn đã viết sai. Latency phân bố rất lệch (long tail): phần lớn request nhanh, nhưng một nhóm nhỏ rất chậm, và chính nhóm chậm đó là người dùng bực bội nhất và hay đăng review 1 sao.

Vì vậy ta luôn dùng percentile:

  • P50 (median): một nửa số request nhanh hơn con số này. Cho biết trải nghiệm "điển hình".
  • P95: 95% request nhanh hơn con số này. Đây là con số quan trọng nhất để cam kết — nó bảo vệ đại đa số người dùng.
  • P99: dành cho các tính năng cực kỳ nhạy cảm; 1% chậm nhất.
Quy ước thực dụng: viết budget chính theo P95. P50 để tham khảo trải nghiệm thường, P99 để biết cái đuôi tệ đến đâu.

Phân rã ngân sách (sub-budget breakdown)

Một pipeline AI điển hình gồm các bước nối tiếp. Đây là cách bạn lập bảng — ví dụ cho một tính năng hỏi-đáp có RAG, tổng budget P95 = 3.000ms:

BướcMô tảBudget (P95)
Network (client → server)Round-trip mạng, TLS, hàng đợi100ms
Pre-processingValidate, sanitize, build prompt50ms
Retrieval (nếu có RAG)Truy vấn vector DB, lấy context200ms
LLM call (TTFT + generation)Gọi model, sinh token2.200ms
Post-processingParse, format, kiểm duyệt output150ms
Render (client)Hiển thị lên UI100ms
Buffer dự phòngĐệm cho biến động200ms
Tổng3.000ms
Khi viết được bảng này, bạn đã làm được điều mà 80% PM không làm: biến một yêu cầu mơ hồ thành hợp đồng kỹ thuật.

TTFT vs TPOT vs total — và sức mạnh của streaming

Với LLM, đừng chỉ nghĩ về "tổng thời gian". Có ba khái niệm tách biệt:

  • TTFT (Time To First Token): thời gian từ lúc gửi request đến lúc token đầu tiên xuất hiện. Đây là thứ người dùng cảm nhận là "nó đã bắt đầu phản hồi chưa".
  • TPOT (Time Per Output Token): thời gian sinh mỗi token tiếp theo. Quyết định tốc độ "chữ chạy".
  • Total latency = TTFT + (TPOT × số token).
Hệ quả thực tế cực lớn: nếu bạn cho streaming (chữ hiện dần ra như ChatGPT) thì cái quan trọng là TTFT thấp (ví dụ dưới 800ms), còn total có thể tới 5-6 giây mà người dùng vẫn thấy ổn vì họ đọc theo lúc chữ chạy. Ngược lại, nếu bạn render một cục (chờ xong hết rồi mới hiện), thì total mới là thứ người dùng chịu đựng, và 5 giây màn hình trắng là không thể chấp nhận. Quyết định streaming hay không là một quyết định trong latency budget, không phải chi tiết kỹ thuật thuần túy.

Latency budget gắn với fallback

Budget không có ý nghĩa nếu không kèm hành động khi vượt budget. Spec phải trả lời: nếu retrieval mất quá 200ms thì sao? Nếu LLM không trả token đầu tiên trong 1,5 giây thì sao? Thường có timeout cứng cho mỗi bước, và khi chạm timeout thì kích hoạt fallback (xuống model nhỏ hơn, dùng cache, hiển thị trả lời mặc định). Phần fallback chi tiết thuộc Bài 31, nên ở đây bạn chỉ cần khai báo timeout cho từng bước trong budget và trỏ sang spec fallback.

Tình huống thực tế

Ví dụ 1 — Tiki: trợ lý tìm kiếm sản phẩm bằng AI

Giả định một đội sản phẩm tại Tiki xây "Trợ lý mua sắm" — người dùng gõ "tìm sữa rửa mặt cho da dầu dưới 200k" và AI trả về gợi ý kèm giải thích ngắn. Bản đầu, đội chỉ viết spec "phản hồi nhanh, dưới 3 giây". Khi đưa ra A/B test, conversion của nhánh có trợ lý thấp hơn nhánh tìm kiếm thường. Đào sâu log mới thấy: P50 là 2,1 giây (ổn) nhưng P95 là 6,8 giây vì những query phức tạp phải gọi retrieval trên catalog hàng triệu SKU rồi mới gọi LLM, và họ render một cục.

Đội viết lại latency budget P95 = 2.500ms: network 80ms, retrieval 250ms (giới hạn top-20 sản phẩm thay vì top-100), LLM với streaming TTFT 700ms, render dần. Quan trọng nhất họ bật streaming: tên sản phẩm hiện ra trước trong 900ms, phần "vì sao gợi ý" chạy chữ sau. Cảm nhận "nhanh" thay đổi hoàn toàn dù tổng thời gian sinh đủ output vẫn ~3 giây. Conversion nhánh AI vượt nhánh cũ ở lần test sau.

Bài học: P95 mới là con số phản ánh trải nghiệm thật, và streaming có thể "mua" cho bạn cảm giác nhanh mà không cần model nhanh hơn.

Ví dụ 2 — Một ngân hàng số tại Việt Nam: chatbot hỗ trợ giao dịch

Một ngân hàng số (giả định, kiểu Cake hay Timo) làm chatbot AI giải đáp "vì sao giao dịch của tôi bị từ chối". Khác với Tiki, bối cảnh này không được sai và phải tra cứu dữ liệu giao dịch thật, nên pipeline dài hơn: auth → truy vấn lịch sử giao dịch (internal API, đôi khi chậm) → RAG trên tài liệu chính sách → LLM → kiểm duyệt output (guardrail) → render.

Khi lập budget P95 = 4.000ms, họ phát hiện bước truy vấn internal API giao dịch có P95 tới 1.800ms vào giờ cao điểm — ngốn gần nửa ngân sách. Nếu không phân rã, họ sẽ đổ lỗi nhầm cho LLM. Giải pháp trong spec: đặt timeout cứng 1.200ms cho bước này, nếu vượt thì fallback sang câu trả lời chung ("Giao dịch có thể bị từ chối do hạn mức hoặc xác thực; bạn kiểm tra giúp...") kèm nút "nói chuyện với nhân viên". Họ cũng cache kết quả RAG cho các câu hỏi phổ biến, kéo bước retrieval từ 400ms xuống 120ms.

Bài học: phân rã ngân sách giúp bạn tìm đúng "thủ phạm" ngốn thời gian (ở đây là internal API chứ không phải LLM), và mỗi bước cần một timeout cứng gắn với fallback.

Ví dụ 3 — Startup SaaS: tính năng tóm tắt email không cần realtime

Một startup B2B làm tính năng "tóm tắt chuỗi email dài thành 3 gạch đầu dòng". Đội PM ban đầu áp cùng tiêu chuẩn khắt khe như chatbot: P95 phải dưới 2 giây, và stress vì model lớn không đạt nổi. Nhưng khi nhìn lại hành vi người dùng, họ nhận ra đây là tác vụ không đồng bộ về mặt cảm nhận: người dùng bấm "Tóm tắt" rồi vẫn cuộn đọc email, không ngồi nhìn chằm chằm.

Họ phân loại lại tính năng là "non-blocking" và đặt budget rộng hơn: P95 = 6 giây, hiển thị skeleton loading có animation, dùng model lớn hơn cho chất lượng tóm tắt tốt hơn. Đổi lại, họ thêm một ràng buộc UX trong spec: nếu vượt 8 giây (P99) thì hiện thông báo "Đang xử lý, chúng tôi sẽ báo khi xong" để tránh cảm giác treo.

Bài học: không phải tính năng AI nào cũng cần budget chặt như nhau. Phân loại theo mức độ người dùng đang chờ chủ động hay không để đặt ngân sách hợp lý — đặt budget quá chặt khiến bạn hy sinh chất lượng một cách vô nghĩa.

Hướng dẫn từng bước

Bước 1 — Phân loại độ nhạy latency của tính năng. Trước khi đặt con số, trả lời: người dùng đang chủ động chờ (foreground, như chat) hay tác vụ chạy nền (background, như tóm tắt)? Mức kỳ vọng khác nhau hoàn toàn. Foreground thường cần TTFT < 1 giây; background có thể nới tới vài giây.

Bước 2 — Đặt tổng budget mục tiêu theo P95. Chọn một con số tổng phản ánh kỳ vọng người dùng và đối thủ. Ví dụ "P95 ≤ 3.000ms cho toàn bộ luồng từ tap đến token đầu tiên". Viết rõ đo từ điểm nào đến điểm nào (from-to), vì "latency" mà không nói mốc đo là vô nghĩa.

Bước 3 — Vẽ pipeline và liệt kê mọi bước nối tiếp. Network, pre-processing, retrieval, LLM (TTFT + generation), post-processing/guardrail, render. Bỏ sót bước nào là budget sẽ vỡ khi chạy thật. Đừng quên các bước "ẩn" như auth, internal API, kiểm duyệt nội dung.

Bước 4 — Phân bổ sub-budget cho từng bước và chừa buffer. Điền con số P95 cho mỗi bước sao cho tổng ≤ budget mục tiêu, và luôn để lại 5-10% làm buffer cho biến động. Nếu tổng các bước đã vượt budget mục tiêu, bạn buộc phải đánh đổi: model nhỏ hơn, cắt số tài liệu retrieval, hoặc bật streaming.

Bước 5 — Tách TTFT và total nếu có streaming. Với LLM, ghi rõ hai dòng: "TTFT P95 ≤ 800ms" và "Total P95 ≤ 4.000ms". Quy định UI dùng streaming hay render một cục — đây là quyết định sản phẩm cần ghi vào spec.

Bước 6 — Gắn timeout và hành vi khi vượt budget. Mỗi bước cần một timeout cứng (thường lớn hơn budget P95 một chút) và một mô tả "khi vượt thì làm gì" — trỏ sang spec fallback (Bài 31). Không có dòng này thì budget chỉ là ước mơ.

Bước 7 — Định nghĩa cách đo và ngưỡng cảnh báo. Ghi rõ team sẽ log latency từng bước thế nào và đặt alert khi nào (ví dụ "cảnh báo nếu P95 tổng vượt 3.500ms trong 10 phút"). Phần này nối với spec monitoring (Bài 43), nhưng budget phải nêu ngưỡng để monitoring biết đường mà canh.

Lỗi thường gặp & mẹo

Lỗi 1 — Cam kết bằng trung bình thay vì P95. "Trung bình 2 giây" giấu đi nhóm 10% người dùng phải chờ 7 giây. Luôn viết budget theo percentile.

Lỗi 2 — Chỉ một con số tổng, không phân rã. Khi chậm, bạn không biết tối ưu chỗ nào. Luôn lập bảng sub-budget để biết "tiền đi đâu".

Lỗi 3 — Quên các bước ẩn. Auth, internal API, guardrail, network round-trip — những bước này âm thầm ngốn thời gian. Như ví dụ ngân hàng, internal API có thể ngốn gần nửa budget mà bạn không ngờ.

Lỗi 4 — Đặt budget cứng cho mọi tính năng. Tóm tắt nền và chat realtime không thể cùng một chuẩn. Phân loại foreground/background trước.

Lỗi 5 — Lờ TTFT, chỉ nhìn total. Với streaming, TTFT mới là thứ quyết định cảm nhận "nó có phản hồi không". Bỏ qua TTFT là bỏ qua đòn bẩy mạnh nhất để cải thiện trải nghiệm.

Lỗi 6 — Budget không kèm hành động khi vượt. Một con số không gắn timeout và fallback chỉ là nguyện vọng. Phải nói rõ "vượt thì làm gì".

Mẹo 1 — Dùng streaming để "mua" cảm giác nhanh. Nếu không thể giảm total, hãy giảm TTFT và cho chữ chạy. Người dùng đọc theo và quên mất là họ đang chờ.

Mẹo 2 — Cache những query phổ biến. Một tỉ lệ lớn câu hỏi lặp lại. Cache có thể kéo retrieval và thậm chí cả LLM call xuống gần bằng 0 cho các trường hợp đó.

Mẹo 3 — Benchmark thật trước khi cam kết. Đừng đoán con số. Chạy thử 100-200 request thật, đo P50/P95/P99 rồi mới chốt budget. Con số trên giấy của nhà cung cấp model luôn đẹp hơn thực tế production của bạn.

Mẹo 4 — Đặt buffer. Production luôn tệ hơn môi trường test. Chừa 5-10% đệm để khỏi vỡ budget vì biến động nhỏ.

Bài tập thực hành

  • Lập bảng sub-budget. Chọn một tính năng AI bạn đang hoặc sẽ làm (chatbot, tóm tắt, gợi ý, tìm kiếm ngữ nghĩa). Vẽ pipeline đầy đủ gồm mọi bước nối tiếp, kể cả các bước ẩn. Đặt tổng budget P95 và phân bổ con số cho từng bước sao cho có chừa 10% buffer. Trình bày dưới dạng bảng như ví dụ trong bài.
  • Phân loại và lý giải. Tính năng của bạn là foreground hay background? Người dùng đang chủ động chờ hay không? Dựa vào đó, lập luận tại sao bạn chọn budget chặt hay nới, và quyết định có dùng streaming không. Viết 3-4 câu giải thích như thể bạn đang thuyết phục tech lead trong buổi review.
  • Viết dòng "khi vượt budget". Cho từng bước trong bảng ở bài 1, viết một timeout cứng và một câu mô tả hành vi khi vượt (fallback gì, hiển thị gì cho người dùng). Đây chính là phần biến budget từ nguyện vọng thành hợp đồng.
  • Tự phản biện. Tưởng tượng internal API của bạn có P95 đột nhiên tăng gấp đôi vào giờ cao điểm. Budget của bạn còn sống không? Nếu không, bạn sẽ điều chỉnh phân bổ hoặc thêm cơ chế gì (cache, timeout, fallback)?

Tóm tắt

Latency budget là cách bạn — với tư cách PM — biến yêu cầu "phải nhanh" mơ hồ thành một hợp đồng số học mà cả team thực thi được. Ba điều cốt lõi cần nhớ:

  • Đo bằng P95, không bao giờ bằng trung bình. Cái đuôi chậm mới là nơi người dùng bực bội và bỏ đi.
  • Phân rã ngân sách theo từng bước (network, retrieval, LLM, post-processing, render) và chừa buffer — để khi chậm bạn biết "tiền đi đâu" mà tối ưu, và để không bỏ sót các bước ẩn như auth hay internal API.
  • Tách TTFT khỏi total và tận dụng streaming. Với LLM, cảm giác nhanh đến từ token đầu tiên xuất hiện sớm, không nhất thiết từ việc sinh xong toàn bộ.
Cuối cùng, budget chỉ sống khi mỗi bước có timeout cứng và hành vi khi vượt. Phân loại tính năng foreground hay background để đặt mức kỳ vọng hợp lý, benchmark thật trước khi cam kết, và nhớ rằng một câu trả lời "đủ tốt" hiện ra nhanh gần như luôn thắng một câu trả lời "hoàn hảo" đến muộn.