Chào mừng bạn đến với bài học cuối cùng trong lộ trình Kỹ Sư AI Thực Chiến. Sau khi đã nghiên cứu, lập trình và tinh chỉnh mô hình ngôn ngữ lớn thành công, bước thử thách cuối cùng để đưa sản phẩm trí tuệ nhân tạo ra thế giới là đưa hệ thống vận hành trên môi trường Production (MLOps).

Đối với ứng dụng Generative AI, MLOps giải quyết 3 bài toán sống còn: Serving mô hình hiệu năng cao chịu tải lớn, Đánh giá chất lượng tự động hóa, và Giám sát gỡ lỗi thời gian thực. Bài học này sẽ hướng dẫn bạn cơ chế quản lý bộ nhớ đệm PagedAttention của vLLM, cách thiết lập bộ chỉ số đánh giá RAG tự động bằng Ragas, cơ chế ghi log suy luận với Phoenix Tracing, và tự lập trình hệ thống đánh giá RAG bằng Python để xuất báo cáo chất lượng.

20.1 Serving mô hình hiệu năng cao với vLLM

Nếu ta deploy mô hình LLM lên server bằng mã nguồn thô của thư viện Transformers (Hugging Face), hệ thống sẽ chạy cực kỳ chậm và nhanh chóng bị lỗi tràn bộ nhớ VRAM (Out of Memory - OOM). Nguyên nhân chính là do kích thước của bộ đệm Key-Value (KV Cache) lưu trữ các token trung gian của mỗi request phình to liên tục theo thời gian thực và phân bổ bộ nhớ bị phân mảnh.

Thư viện vLLM giải quyết triệt để vấn đề này bằng thuật toán PagedAttention (lấy cảm hứng từ cơ chế Quản lý Bộ nhớ ảo phân trang của Hệ điều hành):

  • Phân trang KV Cache: PagedAttention phân chia KV Cache của mỗi request thành các khối bộ nhớ cố định (Blocks). Các khối này không cần nằm liên tiếp nhau trên VRAM vật lý.
  • Bảng tra cứu trang (Page Table): Hệ thống duy trì bảng ánh xạ các token logic của request với các block vật lý, cho phép chia sẻ các khối KV Cache chung giữa các request song song (ví dụ: chia sẻ phần prompt hệ thống hoặc lịch sử hội thoại chung).
  • Tối ưu hóa: Nhờ loại bỏ phân mảnh bộ nhớ, vLLM có thể ép sát hiệu năng VRAM vật lý, giúp tăng tốc độ suy luận (Throughput) lên từ 10 đến 20 lần so với thông thường.
⚠️ Cạm bẫy: Cấu hình VRAM tối đa trong vLLM
Mặc định, khi khởi chạy vLLM sẽ tự động chiếm dụng 90% bộ nhớ GPU vật lý để làm không gian lưu trữ cho PagedAttention thông qua tham số gpu_memory_utilization = 0.90. Nếu bạn deploy vLLM chung trên một máy chủ đang chạy các tác vụ khác (ví dụ: chạy thêm ứng dụng nhúng embeddings hoặc FastAPI), hệ thống sẽ lập tức crash OOM. Giải pháp: Cần cấu hình giới hạn chỉ số này phù hợp (ví dụ: 0.70 hoặc 0.80) để chừa khoảng trống bộ nhớ dự phòng cho các ứng dụng phụ trợ chạy ổn định.

20.2 Đánh giá hệ thống RAG tự động bằng Ragas

Đối với ứng dụng RAG, việc cử con người ngồi đọc hàng vạn câu trả lời của mô hình để chấm điểm chất lượng là vô cùng tốn kém và không khả thi. Do đó, bộ khung đánh giá tự động Ragas (Retrieval Augmented Generation Assessment) đề xuất phương án sử dụng LLM làm giám khảo đánh giá dựa trên 3 chỉ số cốt lõi:

Chỉ số (Metric) Bản chất đo lường Công thức / Cơ chế
Faithfulness Độ trung thực của câu trả lời so với ngữ cảnh tài liệu tìm thấy (Tránh ảo tưởng). Tỷ lệ các khẳng định trong câu trả lời có thể suy diễn trực tiếp từ Context: \[Faithfulness = \frac{\text{Số khẳng định có cơ sở}}{\text{Tổng số khẳng định trong câu trả lời}}\]
Answer Relevance Mức độ câu trả lời giải quyết trực tiếp và đúng trọng tâm câu hỏi của người dùng. Tính trung bình Cosine Similarity giữa embedding của câu hỏi gốc và embedding của các câu hỏi giả lập do LLM sinh ngược lại từ câu trả lời.
Context Recall Khả năng của bộ truy xuất tìm kiếm đầy đủ thông tin từ tài liệu gốc (Ground Truth). Tỷ lệ các thông tin chính trong Ground Truth được phủ trong Context tìm được: \[Context Recall = \frac{\text{Số sự thật trong Ground Truth được phủ}}{\text{Tổng số sự thật trong Ground Truth}}\]

20.3 Tracing & Giám sát luồng suy luận với Phoenix

Khi một Agent hoạt động sai hoặc đưa ra câu trả lời lạc đề, rất khó để biết lỗi xảy ra ở bước nào (do RAG quét sai tài liệu, do LLM dịch sai câu hỏi, hay do Agent gọi nhầm công cụ). Chúng ta cần một hệ thống Tracing (Theo vết) giống như "hộp đen máy bay" để ghi chép lại mọi hành động.

Tích hợp thư viện Arize Phoenix hoặc LangSmith cho phép ứng dụng tự động ghi nhận:

  • Thời gian thực thi (Latency) của từng Node.
  • Số lượng Token tiêu thụ ở mỗi bước (để kiểm soát chi phí API).
  • Chuỗi Prompt thô và kết quả phản hồi thô ở từng lượt gọi LLM phục vụ khâu gỡ lỗi (Debugging).
⚡ Ranh giới bảo mật dữ liệu: Lọc thông tin nhạy cảm
Hệ thống Tracing lưu lại toàn bộ nội dung prompt đầu vào của người dùng. Nếu người dùng nhập mật khẩu, số thẻ ngân hàng, hoặc thông tin cá nhân (PII), các dữ liệu nhạy cảm này sẽ bị phơi bày trên dashboard giám sát. Để đảm bảo an toàn và tuân thủ các chính sách bảo mật dữ liệu toàn cầu (GDPR), bắt buộc phải lập trình bộ lọc (Redaction Filter) thay thế các thông tin nhạy cảm này bằng ký tự ẩn danh trước khi đẩy lên log server.

20.4 Dự án thực hành bài 20: Tự lập trình hệ thống đánh giá RAG bằng Python

Dự án thực hành cuối cùng là tự tay viết một Chương trình đánh giá RAG tự động bằng Python.

Hệ thống sẽ mô phỏng việc tính toán các chỉ số Faithfulness, Context Recall, và Answer Relevance dựa trên thuật toán so khớp thực thể và từ khóa giao thoa Jaccard Index. Sau khi chạy đánh giá qua danh sách các lượt truy vấn, chương trình sẽ tự động xuất ra một báo cáo chất lượng hệ thống (RAG Quality Evaluation Report) định dạng Markdown hoàn chỉnh.

rag_evaluator.py
import json
import re
 
# 1. Tập dữ liệu kiểm thử giả lập (Test dataset)
# Mỗi bản ghi chứa: Câu hỏi, Context tìm được, Câu trả lời của LLM, và Đáp án chuẩn (Ground Truth)
TEST_DATASET = [
    {
        "question": "Thuật toán PagedAttention trong vLLM dùng để làm gì?",
        "context": "vLLM sử dụng thuật toán PagedAttention để phân chia bộ đệm Key-Value (KV Cache) thành các khối cố định trên bộ nhớ VRAM, giúp loại bỏ hiện tượng phân mảnh bộ nhớ vật lý.",
        "answer": "PagedAttention dùng để phân chia bộ đệm KV Cache thành các khối bộ nhớ cố định giúp tối ưu hóa VRAM và tránh phân mảnh.",
        "ground_truth": "PagedAttention dùng để giải quyết vấn đề phân mảnh bộ nhớ KV Cache bằng cách chia nhỏ nó thành các khối cố định trên GPU VRAM."
    },
    {
        "question": "Định nghĩa kỹ thuật LoRA?",
        "context": "LoRA là kỹ thuật đóng băng ma trận trọng số gốc của LLM và đưa vào các ma trận hạng thấp song song để huấn luyện, giúp giảm hàng trăm lần số lượng tham số cập nhật.",
        "answer": "LoRA là kỹ thuật tăng nhiệt độ temperature để mô hình ngôn ngữ lớn hoạt động sáng tạo hơn.",
        "ground_truth": "LoRA là kỹ thuật tinh chỉnh tham số hiệu quả bằng cách huấn luyện các ma trận phân rã hạng thấp song song và đóng băng trọng số gốc."
    }
]
 
# Helper function làm sạch văn bản và bóc tách các từ khóa/thực thể
def get_keywords(text):
    text = text.lower()
    # Loại bỏ dấu câu và bóc tách thành tập các từ đơn
    words = re.findall(r'\b\w+\b', text)
    # Lọc bỏ các stopword tiếng Việt cơ bản để giữ lại từ mang ngữ nghĩa
    stopwords = {"và", "để", "của", "là", "trong", "cho", "có", "các", "được", "bằng", "với", "ra"}
    return set([w for w in words if w not in stopwords])
 
# 2. Hàm tính toán chỉ số Faithfulness (Độ trung thực)
def calculate_faithfulness(answer, context):
    ans_words = get_keywords(answer)
    ctx_words = get_keywords(context)
    
    if not ans_words:
        return 0.0
        
    # Tính tỷ lệ từ khóa của câu trả lời nằm trong tài liệu Context dẫn chứng
    overlap = ans_words.intersection(ctx_words)
    return len(overlap) / len(ans_words)
 
# 3. Hàm tính toán chỉ số Context Recall
def calculate_context_recall(context, ground_truth):
    ctx_words = get_keywords(context)
    gt_words = get_keywords(ground_truth)
    
    if not gt_words:
        return 0.0
        
    # Tính tỷ lệ thông tin vàng trong Ground Truth được phủ trong tài liệu Context tìm được
    overlap = gt_words.intersection(ctx_words)
    return len(overlap) / len(gt_words)
 
# 4. Hàm tính toán chỉ số Answer Relevance (Sự liên quan)
def calculate_answer_relevance(question, answer):
    q_words = get_keywords(question)
    ans_words = get_keywords(answer)
    
    if not q_words:
        return 0.0
        
    # Tính độ giao thoa tương quan giữa từ khóa câu hỏi và câu trả lời (Jaccard Index)
    intersection = q_words.intersection(ans_words)
    union = q_words.union(ans_words)
    return len(intersection) / len(union)
 
# 5. Hàm chạy đánh giá toàn diện hệ thống
def run_evaluation():
    print("🚀 Đang khởi chạy hệ thống đánh giá RAG tự động...\n")
    
    report_data = []
    
    sum_faithfulness = 0.0
    sum_recall = 0.0
    sum_relevance = 0.0
    
    for idx, sample in enumerate(TEST_DATASET):
        f_score = calculate_faithfulness(sample["answer"], sample["context"])
        rec_score = calculate_context_recall(sample["context"], sample["ground_truth"])
        rel_score = calculate_answer_relevance(sample["question"], sample["answer"])
        
        sum_faithfulness += f_score
        sum_recall += rec_score
        sum_relevance += rel_score
        
        print(f"Lượt Test {idx+1}:")
        print(f"  - Faithfulness: {f_score:.2%}")
        print(f"  - Context Recall: {rec_score:.2%}")
        print(f"  - Answer Relevance: {rel_score:.2%}\n")
        
        report_data.append({
            "id": idx + 1,
            "question": sample["question"],
            "faithfulness": f_score,
            "context_recall": rec_score,
            "answer_relevance": rel_score
        })
        
    n = len(TEST_DATASET)
    avg_f = sum_faithfulness / n
    avg_rec = sum_recall / n
    avg_rel = sum_relevance / n
    
    # 6. Xuất file báo cáo Markdown chất lượng RAG
    report_filename = "rag_evaluation_report.md"
    with open(report_filename, "w", encoding="utf-8") as f:
        f.write("# 📊 Báo Cáo Đánh Giá Chất Lượng Đường Ống RAG\n\n")
        f.write("## 1. Điểm số trung bình toàn hệ thống\n")
        f.write(f"- **Faithfulness Score (Độ trung thực):** {avg_f:.2%}\n")
        f.write(f"- **Context Recall Score (Độ phủ):** {avg_rec:.2%}\n")
        f.write(f"- **Answer Relevance Score (Đúng trọng tâm):** {avg_rel:.2%}\n\n")
        
        f.write("## 2. Chi tiết kết quả từng mẫu test\n")
        f.write("| Lượt test | Faithfulness | Context Recall | Answer Relevance |\n")
        f.write("| :---: | :---: | :---: | :---: |\n")
        for item in report_data:
            f.write(f"| {item['id']} | {item['faithfulness']:.2%} | {item['context_recall']:.2%} | {item['answer_relevance']:.2%} |\n")
            
    print(f"🏆 Đã xuất thành công báo cáo đánh giá vào tệp '{report_filename}'!")

if __name__ == "__main__":
    run_evaluation()
🔢 Kết quả chạy thật: bộ chỉ số có bắt được câu trả lời ảo tưởng không?
Chạy chính xác đoạn code trên (đã kiểm chứng bằng Python) cho ra kết quả rất đáng chú ý: Lượt Test 2 cố tình chứa câu trả lời sai hoàn toàn ("LoRA là kỹ thuật tăng nhiệt độ temperature...") dù Context và Ground Truth đều nói đúng về LoRA. Điểm số đo được:
  • Lượt Test 1 (câu trả lời đúng): Faithfulness = $75.00\%$, Context Recall = $60.87\%$.
  • Lượt Test 2 (câu trả lời ảo tưởng/sai): Faithfulness chỉ còn $17.65\%$ — giảm hẳn so với Test 1, dù Context Recall vẫn ở mức $69.57\%$ (vì bộ truy xuất tài liệu vẫn tìm đúng, chỉ có LLM tự bịa câu trả lời).
Con số này xác nhận trực quan đúng nội dung câu hỏi 2 trong phần Trắc nghiệm gốc của bài học: khi Faithfulness thấp mà Context Recall vẫn cao, lỗi nằm ở khâu sinh câu trả lời (Generation) của LLM chứ không phải ở khâu truy xuất tài liệu (Retrieval).
⚠️ Giới hạn phát hiện khi chạy thử: Answer Relevance kiểu Jaccard đánh giá sai câu trả lời đúng
Điểm Answer Relevance đo được ở Lượt Test 1 (câu trả lời hoàn toàn chính xác, đúng trọng tâm) chỉ đạt $8.00\%$ — một con số cực kỳ thấp trông như câu trả lời lạc đề, trong khi thực chất câu trả lời rất tốt. Nguyên nhân: hàm calculate_answer_relevance dùng chỉ số Jaccard trên tập từ khóa thô của câu hỏi và câu trả lời — nhưng một câu trả lời tốt thường diễn đạt lại ý bằng từ ngữ khác hẳn câu hỏi (ví dụ câu hỏi dùng "PagedAttention...dùng để làm gì" còn câu trả lời dùng "phân chia bộ đệm... giúp tối ưu hóa"), nên gần như không có từ khóa trùng lặp trực tiếp. Đây chính là lý do Ragas thật không dùng Jaccard mà dùng phương pháp phức tạp hơn nhiều: yêu cầu LLM tự sinh ngược lại một câu hỏi giả lập từ câu trả lời, rồi so sánh độ tương đồng Cosine giữa embedding của câu hỏi giả lập đó với câu hỏi gốc (như mô tả ở bảng công thức mục 20.2) — cách này nắm bắt được sự tương đồng NGỮ NGHĨA thay vì chỉ đếm từ trùng lặp. Bài học: một phép đo giả lập đơn giản hóa để dễ dạy có thể cho ra con số gây hiểu lầm nghiêm trọng nếu đem so sánh trực tiếp với ngưỡng đánh giá thực tế.

Tóm tắt bài học & Cầu nối kiến thức

🔑 Bài học đạt được:
  • Đạt được: Làm chủ cơ chế PagedAttention của vLLM để tối ưu hóa bộ nhớ đệm KV Cache phục vụ tải lớn.
  • Đạt được: Thiết lập bộ chỉ số đánh giá RAG tự động bằng Ragas và giám sát gỡ lỗi Agent bằng Phoenix Tracing.

Cầu nối bài tiếp theo: Chúc mừng bạn đã hoàn thành xuất sắc toàn bộ lộ trình 20 bài học của Kỹ Sư AI Thực Chiến! Hành trình tiếp theo của bạn là ứng dụng những kiến thức nền tảng này để xây dựng các giải pháp AI độc lập, đóng góp vào cộng đồng và không ngừng học hỏi trước làn sóng công nghệ mới.

Tải file code thực hành minh họa bài học

File Python rag_evaluator.py — mã nguồn thuật toán so khớp thực thể và từ khóa giao thoa tự động tính toán điểm số Faithfulness, Context Recall và Answer Relevance (chạy python rag_evaluator.py):

Tải về rag_evaluator.py

📖 Tài liệu tham khảo

Bài viết liên quan trong series

Bài 19: Tinh chỉnh mô hình (Fine-tuning LLM & LoRA) Quay lại Lộ trình Kỹ Sư AI Thực Chiến

Bình luận