Ứng dụng LLMgiáo trình tự học
Chương 317 phút đọcLàm việc với mô hình ngôn ngữ

3Chọn và triển khai mô hình#

Chương 2 nói về những đặc điểm chung của mọi mô hình ngôn ngữ lớn. Khi bắt tay làm, ta không làm việc với "một LLM" chung chung mà phải chọn một mô hình cụ thể trong hàng trăm mô hình, mỗi mô hình có giấy phép, giá, tốc độ và năng lực riêng, rồi quyết định gọi nó qua API của nhà cung cấp hay tự chạy trên hạ tầng của mình. Quyết định này kéo theo cách tính chi phí, nơi dữ liệu đi qua và công sức vận hành, nên cần được cân nhắc từ đầu. Ta bắt đầu bằng việc phân loại các mô hình.

3.1Các loại mô hình#

Các giai đoạn huấn luyện ở Mục 2.4 cũng là cách phân loại tự nhiên nhất cho những mô hình ngôn ngữ mà người xây ứng dụng gặp. Mô hình gốc chỉ qua tiền huấn luyện, nên nó viết tiếp văn bản chứ không trả lời câu hỏi: đưa vào "Thủ đô của Pháp là gì?", mô hình có thể viết tiếp thành một danh sách các câu hỏi khác. Mô hình gốc thường là điểm xuất phát để tinh chỉnh và hiếm khi được dùng trực tiếp trong sản phẩm. Mô hình chỉ dẫn (instruct hoặc chat model) đã được tinh chỉnh để làm theo chỉ dẫn và trò chuyện, và đây là loại có mặt trong phần lớn ứng dụng. Mô hình suy luận được huấn luyện thêm để sinh một chuỗi suy luận trước khi trả lời. Nó hợp với các bài toán nhiều bước như toán, lập trình hay phân tích, nhưng tiêu nhiều token và chậm hơn, nên không phải lựa chọn tốt cho những tác vụ đơn giản cần phản hồi nhanh.

Một ứng dụng thật hiếm khi chỉ dùng mô hình văn bản. Đi kèm với nó thường có mô hình embedding (Chương 6), mô hình xếp lại (Mục 8.4), mô hình nhận dạng tiếng nói, mô hình hiểu ảnh (Chương 14) và mô hình kiểm duyệt nội dung (Mục 11.5).

3.2Mô hình đóng và mô hình mở trọng số#

Câu hỏi tiếp theo là ai giữ trọng số. Mô hình đóng (closed model) chỉ dùng được qua API của nhà phát triển, và trọng số không được công bố. Các họ mô hình đóng phổ biến gồm Claude của Anthropic, Gemini của Google, GPT và o-series của OpenAI, cùng các mô hình của Cohere và một số mô hình của Mistral. Mô hình mở trọng số (open-weight model) thì có trọng số được công bố để tải về, chạy trên hạ tầng riêng, và thường được phép tinh chỉnh. Các họ phổ biến gồm Llama của Meta, Qwen của Alibaba, Gemma của Google, DeepSeek và một số mô hình của Mistral.

Mở trọng số không đồng nghĩa với mã nguồn mở (open source). Theo Open Source AI Definition do Open Source Initiative công bố năm 2024, một hệ thống AI mã nguồn mở phải cung cấp đủ thông tin về dữ liệu huấn luyện, mã huấn luyện và trọng số để người khác tái tạo và sửa đổi được nó. Phần lớn mô hình "mở" hiện nay chỉ công bố trọng số mà không công bố dữ liệu huấn luyện. Giấy phép cũng rất khác nhau. Một số mô hình dùng giấy phép rộng rãi như Apache 2.0 hoặc MIT. Một số dùng giấy phép riêng có điều kiện; giấy phép cộng đồng của Llama chẳng hạn yêu cầu xin giấy phép riêng nếu sản phẩm có trên 700 triệu người dùng hoạt động hằng tháng, kèm một chính sách sử dụng chấp nhận được. Trước khi đưa một mô hình vào sản phẩm thương mại, cần đọc giấy phép của đúng phiên bản mô hình đó.

Bảng dưới so sánh hai cách dùng mô hình theo những tiêu chí thường được cân nhắc.

Tiêu chí Mô hình đóng qua API Mô hình mở trọng số tự triển khai
Chất lượng ở các nhiệm vụ khó thường cao nhất tại mỗi thời điểm thường theo sau một khoảng, khoảng cách thay đổi theo thời gian
Chi phí ban đầu gần bằng 0 cần GPU và công vận hành
Chi phí theo lượng dùng theo token, dễ dự đoán gần như cố định theo hạ tầng; rẻ hơn khi lưu lượng lớn và ổn định
Dữ liệu gửi ra ngoài cho nhà cung cấp ở lại trong hạ tầng của mình
Kiểm soát phiên bản có thể bị ngừng hoặc thay đổi cố định phiên bản, tinh chỉnh tuỳ ý
Vận hành nhà cung cấp lo tự lo: mở rộng, giám sát, cập nhật

Không có lựa chọn nào đúng cho mọi trường hợp. Lý do mạnh nhất để tự triển khai là dữ liệu nhạy cảm không được rời khỏi hạ tầng, như trong y tế, tài chính hay quốc phòng. Ngoài trường hợp đó, quyết định thường đi theo giai đoạn của sản phẩm. Khi mới xây và chưa biết lưu lượng, dùng API cho phép thử nghiệm nhanh mà không tốn chi phí ban đầu. Khi lưu lượng đã lớn và ổn định, tự triển khai một mô hình mở, có thể đã được tinh chỉnh cho một nhiệm vụ hẹp, thường rẻ hơn.

3.3Tự triển khai mô hình#

Việc đầu tiên khi tự triển khai là ước lượng bộ nhớ GPU. Bộ nhớ đó gồm ba phần: trọng số, KV cache và bộ nhớ trung gian khi tính.

Phần trọng số dễ tính nhất: số tham số nhân số byte mỗi tham số. Mô hình 8 tỉ tham số cần khoảng 16 GB ở dạng 16 bit, 8 GB ở dạng 8 bit, và khoảng 4 tới 5 GB ở dạng 4 bit (4 bit là 0,5 byte, cộng thêm các hệ số tỉ lệ của lượng tử hoá). Giáo trình Quantization trình bày cách giảm số bit mà vẫn giữ chất lượng; Mục 11.4 của Quantization nói về GPTQ và AWQ, hai phương pháp lượng tử 4 bit phổ biến cho LLM.

KV cache tỉ lệ với độ dài ngữ cảnh và với số yêu cầu phục vụ đồng thời (Mục 2.2). Phục vụ 16 yêu cầu đồng thời, mỗi yêu cầu 8 nghìn token, với Llama 3 8B cần 16×1=1616 \times 1 = 16 GiB chỉ riêng cho KV cache, xấp xỉ dung lượng trọng số ở dạng 16 bit. Phần còn lại là các kích hoạt tạm thời trong lúc tính. Khi suy luận, phần này thường nhỏ hơn hai phần trên, nhưng vẫn cần chừa một khoảng dự phòng.

Ví dụ 3.1

Phục vụ một mô hình 8 tỉ tham số lượng tử 4 bit (khoảng 5 GB trọng số) cho 8 người dùng đồng thời, mỗi người ngữ cảnh 8 nghìn token (8 × 1 GiB KV cache ở dạng 16 bit, tức khoảng 8,6 GB), cần khoảng 13,6 GB cộng thêm dự phòng, vừa với một GPU 16 GB hoặc 24 GB. Cùng mô hình ở dạng 16 bit cần khoảng 16 + 8,6 ≈ 24,6 GB cộng dự phòng, không vừa GPU 24 GB. Lượng tử hoá KV cache xuống 8 bit giảm thêm một nửa phần KV cache (Mục 11.7 của Quantization).

Có nhiều công cụ để chạy mô hình mở, từ đơn giản tới phức tạp. Đơn giản nhất là Ollama: một lệnh là chạy được mô hình trên máy cá nhân, công cụ tự tải bản đã lượng tử hoá từ thư viện của nó và mở một API cục bộ tương thích với định dạng API của OpenAI, rất tiện cho thử nghiệm và phát triển. LM Studio làm việc tương tự qua một ứng dụng có giao diện đồ hoạ để tìm, tải và chạy mô hình (định dạng GGUF, và MLX trên máy Apple), đồng thời mở máy chủ cục bộ tương thích API OpenAI. Bên dưới nhiều công cụ loại này là llama.cpp, thư viện C/C++ chạy mô hình trên CPU và GPU phổ thông với định dạng GGUF và nhiều mức lượng tử hoá; nhiều công cụ khác, trong đó có Ollama, xây trên nó hoặc từng xây trên nó.

Trong Python, thư viện Hugging Face Transformers tải và chạy được hầu hết mô hình mở. Nó linh hoạt nhưng không được tối ưu để phục vụ nhiều người dùng cùng lúc. Khi cần phục vụ thật, người ta dùng vLLM hoặc các máy chủ suy luận tương tự. vLLM quản lý KV cache theo trang (PagedAttention; Kwon và cộng sự, 2023) và gộp lô liên tục (continuous batching) để phục vụ nhiều yêu cầu đồng thời với thông lượng cao, và cũng có API tương thích OpenAI.

Vì nhiều công cụ cùng cung cấp API theo định dạng của OpenAI, mã ứng dụng có thể chuyển giữa mô hình gọi qua API và mô hình tự triển khai chỉ bằng cách đổi địa chỉ máy chủ và tên mô hình. Nhờ vậy quyết định ở Mục 3.2 bớt rủi ro: có thể bắt đầu với API, rồi chuyển sang tự triển khai khi lưu lượng đủ lớn, mà không phải viết lại ứng dụng.

3.4Hệ sinh thái Hugging Face#

Phần lớn mô hình mở được phân phối qua Hugging Face, nơi lưu trữ và chia sẻ mô hình, dữ liệu và ứng dụng học máy lớn nhất hiện nay. Trung tâm của nó là Hub, kho chứa mô hình, bộ dữ liệu và các ứng dụng demo (Spaces). Mỗi mô hình trên Hub có một model card mô tả mục đích, dữ liệu huấn luyện, giấy phép, hạn chế và cách dùng. Đọc model card là việc bắt buộc trước khi dùng một mô hình, vì nó cho biết định dạng prompt đúng, các tiền tố cần thêm (chẳng hạn các mô hình E5 yêu cầu thêm "query: " và "passage: ", Mục 6.2) và những giới hạn đã biết. Khi cần một mô hình nhỏ chuyên cho một nhiệm vụ thay vì dùng LLM đa năng, trang Tasks phân loại mô hình theo nhiệm vụ (sinh văn bản, phân loại, trích xuất thực thể, nhận dạng tiếng nói, phân loại ảnh...) kèm mô tả và các mô hình tiêu biểu.

Để chạy mô hình, thư viện Transformers cho phép tải và dùng mô hình bằng Python với vài dòng mã; mọi thí nghiệm có dùng mô hình trong giáo trình này đều dùng thư viện đó. Transformers.js làm việc tương tự trong trình duyệt hoặc Node.js, phù hợp với các mô hình nhỏ cần chạy ngay trên máy người dùng. Nếu không muốn tự triển khai, có thể gọi mô hình trên Hub qua dịch vụ suy luận của Hugging Face, thông qua thư viện huggingface_hub.

3.5Gọi mô hình qua API#

Dù gọi mô hình đóng hay mô hình tự triển khai, các API trò chuyện đều theo cùng một cấu trúc: ứng dụng gửi một danh sách tin nhắn, mỗi tin nhắn mang một vai (role), và nhận về tin nhắn tiếp theo của mô hình. Đoạn mã dưới gọi mô hình Qwen2.5 0,5B chạy cục bộ bằng Ollama, qua thư viện Python của OpenAI.

Python
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="khong-can")   # ví dụ: Ollama chạy cục bộ

tra_loi = client.chat.completions.create(
    model="qwen2.5:0.5b-instruct",
    messages=[
        {"role": "system", "content": "Bạn là trợ lý giải thích thuật ngữ học máy, trả lời ngắn gọn."},
        {"role": "user", "content": "Overfitting là gì?"},
    ],
    temperature=0.2,
    max_tokens=200,
)
print(tra_loi.choices[0].message.content)

Tin nhắn có vai system chứa chỉ dẫn hệ thống: vai trò, quy tắc và định dạng đầu ra. Mô hình được huấn luyện để ưu tiên chỉ dẫn này hơn tin nhắn của người dùng. Vai user chứa yêu cầu của người dùng, còn vai assistant chứa các câu trả lời trước đó của mô hình. Vì mô hình không giữ lại gì giữa các lần gọi (Mục 2.2), muốn nó "nhớ" cuộc hội thoại thì ứng dụng phải gửi lại toàn bộ các tin nhắn trước trong mỗi lần gọi.

Chi tiết khác nhau giữa các nhà cung cấp. API Messages của Anthropic đặt chỉ dẫn hệ thống ở một tham số riêng, API Responses của OpenAI có thể lưu trạng thái hội thoại phía máy chủ, API Gemini dùng cấu trúc riêng của nó. Các dịch vụ trung gian như OpenRouter cung cấp một API duy nhất theo định dạng OpenAI để gọi mô hình của nhiều nhà cung cấp, và có thể chuyển sang nhà cung cấp khác khi một nhà cung cấp gặp sự cố.

Đoạn mã trên đủ cho một bản thử, nhưng ứng dụng thật còn phải xử lý những lỗi mà bản thử bỏ qua. Khi vượt giới hạn tốc độ gọi (mã HTTP 429), ứng dụng cần thử lại với thời gian chờ tăng dần theo cấp số nhân. Mỗi lần gọi cần một giới hạn thời gian chờ để không bị treo. Khi nội dung bị bộ lọc an toàn của nhà cung cấp từ chối, cần có một phản hồi dự phòng. Và khi đầu ra bị cắt vì chạm giới hạn max_tokens, ứng dụng phải nhận ra điều đó qua lý do kết thúc (finish reason) thay vì coi đó là một câu trả lời hoàn chỉnh.

3.6Quy trình chọn mô hình#

Đến đây ta có đủ yếu tố để chọn mô hình cho một ứng dụng cụ thể, và nên xét chúng theo một thứ tự nhất định. Đầu tiên là các ràng buộc cứng: dữ liệu có được rời khỏi hạ tầng không, có yêu cầu về nơi lưu trữ dữ liệu không, giấy phép có cho phép mục đích sử dụng không. Những ràng buộc này loại bớt lựa chọn trước khi xét tới chất lượng. Với các mô hình còn lại, chất lượng phải được đo trên nhiệm vụ của chính ứng dụng, bằng bộ đánh giá riêng (Chương 12), chứ không chỉ dựa vào bảng xếp hạng công khai. Sau chất lượng là độ trễ: ứng dụng trò chuyện cần thời gian tới token đầu tiên dưới khoảng một giây, còn xử lý nền theo lô có thể chấp nhận vài phút. Tiếp theo là chi phí ở lưu lượng dự kiến, tính như Ví dụ 2.1. Cuối cùng là những khả năng đặc thù mà ứng dụng cần, như độ dài ngữ cảnh, chất lượng tiếng Việt, function calling, đầu ra có cấu trúc hay xử lý ảnh.

Bảng xếp hạng công khai vẫn có ích ở bước đầu. Các bộ đánh giá như MMLU (kiến thức đa lĩnh vực), GPQA (câu hỏi khoa học khó), SWE-bench (sửa lỗi trong kho mã thật), hay bảng xếp hạng dựa trên bình chọn của người dùng như LMArena, cho một bức tranh chung về năng lực của các mô hình. Tuy vậy chúng có ba hạn chế. Câu hỏi của bộ đánh giá công khai có thể đã lọt vào dữ liệu huấn luyện, hiện tượng gọi là nhiễm dữ liệu (contamination), làm điểm số cao hơn năng lực thật. Khi một con số trở thành mục tiêu tối ưu của các nhà phát triển, nó mất dần giá trị đo lường. Và nhiệm vụ của bộ đánh giá hiếm khi giống nhiệm vụ của ứng dụng, nhất là với tiếng Việt và các lĩnh vực chuyên môn. Vì vậy bảng xếp hạng chỉ nên dùng để chọn ra vài ứng viên, còn quyết định cuối cùng dựa vào bộ đánh giá riêng.

Ứng dụng cũng không bắt buộc dùng một mô hình cho mọi việc. Một mẫu thiết kế phổ biến là định tuyến (routing): một bước phân loại rẻ quyết định yêu cầu đơn giản đi tới mô hình nhỏ và rẻ, yêu cầu phức tạp đi tới mô hình lớn. Một mẫu khác là tầng (cascade): thử mô hình rẻ trước, và chỉ gọi mô hình đắt khi kết quả của mô hình rẻ không qua được bước kiểm tra. Khi phần lớn yêu cầu là đơn giản, hai mẫu này giảm chi phí đáng kể mà không làm giảm chất lượng tổng thể, với điều kiện bước phân loại hoặc bước kiểm tra đủ tin cậy.

3.7Tóm tắt#

Theo giai đoạn huấn luyện, mô hình ngôn ngữ chia thành mô hình gốc, mô hình chỉ dẫn và mô hình suy luận; theo cách phân phối, chúng chia thành mô hình đóng dùng qua API và mô hình mở trọng số tự triển khai được. Tự triển khai đòi hỏi tính bộ nhớ GPU cho trọng số và KV cache, và lượng tử hoá là cách chính để đưa mô hình vừa phần cứng. Các API trò chuyện đều nhận một danh sách tin nhắn có vai, và nhiều công cụ tự triển khai dùng chung định dạng của OpenAI nên việc chuyển đổi giữa chúng khá dễ. Mô hình được chọn bằng cách loại theo ràng buộc cứng trước, đo chất lượng trên bộ đánh giá riêng, rồi mới xét độ trễ, chi phí và các khả năng đặc thù.

Chọn xong mô hình, việc tiếp theo là nói cho nó biết phải làm gì. Với một mô hình chỉ dẫn, kênh giao tiếp duy nhất là văn bản trong prompt, và Chương 4 xét cách viết văn bản đó.


Tự kiểm tra

3 câu. Chọn đáp án để xem ngay lời giải thích.

  1. 1Khác biệt giữa mô hình mở trọng số và mô hình mã nguồn mở theo định nghĩa của OSI là gì?

  2. 2Mô hình 8 tỉ tham số lượng tử 4 bit cần khoảng bao nhiêu bộ nhớ cho trọng số?

  3. 3Vì sao không nên chọn mô hình chỉ dựa vào bảng xếp hạng công khai?

Đã trả lời 0/3