Trời tính không bằng máy tính

blog, khám phá

Fine tune model – viết truyện phong cách Nam Cao

  • Chú ý (1): Nội dung bài viết có nói tới việc thuê máy chủ Google Cloud, mất tiền. Nếu bạn không chắc thì đừng làm theo, mình không chịu trách nhiệm nếu bạn mất tiền oan!
  • Chú ý (2): Bài viết này là để tìm hiểu và học hỏi, mình sẽ không chia sẻ model hay dataset vì model này có thể gây hại (nó có thể sẽ bịa đặt thông tin, phân biệt giới tính,…)


Trong 2 số bài viết trước, mình đã tìm hiểu về cách hoạt động của các model xử lý ngôn ngữ tự nhiên thông dụng, cũng như đã đi sâu vào tìm hiểu về Transformer – cấu trúc cốt lõi của các model nổi tiếng hiện nay như GPT, LLaMA, Gemini,…

Tuy vậy, chỉ “học” mà không có “hành” thì cũng hơi chán. Ở số này, mình sẽ thuật lại quá trình mình fine tune (tinh chỉnh) lại model Vistral để nó có thể viết truyện giống với phong cách Nam Cao.

Vì sao cần fine tune?

Một cách dễ hiểu, fine tune là quá trình bạn lấy một model đã có sẵn, sau đó đưa thêm dữ liệu vào để thay đổi hành vi của nó theo ý muốn. Ví dụ trong bài này, model có sẵn có thể biết viết truyện nói chung, thậm chí biết Nam Cao là ai, nhưng không biết viết truyện theo phong cách cụ thể.

Tuy việc fine tune dễ hiểu, nhưng có nhiều sự hiểu nhầm mà mình muốn chỉ ra như sau:

  • Fine tune không phải là để bạn dạy thêm một đống kiến thức mới cho model. Ví dụ, bạn muốn model chuyên nói về những bài viết trên Nui Blog chẳng hạn, nếu bạn chỉ đưa một đống bài viết blog vào thì nó sẽ hầu hết là nhớ văn phong, chứ không nhớ nhiều về kiến thức. Nếu bạn trong trường hợp này, hãy sử dụng RAG (retrieval augmented generation) thay thì fine tune.
  • Fine tune có khả năng làm mất một vài kiến thức đã có bên trong model. Xin nhớ rằng, nếu bạn fine tune một model có 7B parameters, thì trước sau gì nó cũng chỉ có đúng chừng đó không thêm không bớt.
  • Fine tune không phải là thần kỳ (không phải đổ hết một đống data vào là xong), mà vẫn cần có sự chuẩn bị và sàng lọc. Chúng ta sẽ tìm hiểu ở phần tiếp theo đây.
  • Nếu bạn muốn dạy khá nhiều kiến thức mới cho model, ví dụ model đã biết nói tiếng Anh nhưng bạn dạy thêm tiếng Việt, thì có thể fine tune nhưng sẽ tốn rất, rất nhiều thời gian (gần như tương đương với việc train lại từ đầu). Vậy tốt nhất là nên bắt đầu với model đã có sẵn kiến thức nền về thứ bạn muốn fine tune.
Trước fine tuningSau fine tuning
User: Bạn có biết buồn không?
Assistant: Dù tôi là một trí tuệ nhân tạo và không có cảm xúc như con người, nhưng tôi có thể hiểu và đồng cảm với cảm xúc của con người, bao gồm cả sự buồn bã.
User: Bạn có biết buồn không?
Assistant: Buồn là một cảm xúc mà ai cx trải qua, kể cả mèo như t đây. Đôi khi, nhìn những khoảnh khắc buồn của mọi ng xung quanh, t cx hiểu đc cảm giác đó. Nhưng thường thì, t thích giữ tâm trạng tích cực hơn, vì cuộc sống này rất đẹp và đáng để tận hưởng!

Chuẩn bị dataset

Xác định hành vi mong muốn

Đối với những người mới biết về fine tune, hầu hết mọi người thường nghĩ rằng chỉ cần đổ 1 đống data vào là xong. Ví dụ ở đây, mình muốn cho model học văn Nam Cao, vậy thì chỉ đơn giản là đổ 1 đống truyện ngắn Nam Cao vào là xong, phải không? Mặc dù theo lý thuyết là được, nhưng thực tế khi sử dụng model sau khi fine tune, bạn sẽ rất khó để bảo model viết truyện theo một chủ đề có sẵn.

Hành vi ở đây mà ta mong muốn đó là:

  • Input: Mình yêu cầu model viết theo chủ đề abc xyz
  • Output: Câu chuyện viết theo chủ đề abc xyz với phong cách của Nam Cao

Vậy thì trong quá trình fine tune, dataset phải theo dạng:

  • Output: Câu chuyện viết theo chủ đề abc xyz với phong cách của Nam Cao
  • Input: Chủ đề của câu chuyện nói trên ==> đơn giản nhất, mình chỉ cần lấy tóm tắt của đoạn văn trên

Chú ý là mình không tóm tắt cả một truyện thành 1 đoạn văn, mà mình chia một truyện ra làm nhiều đoạn nhỏ, rồi tóm tắt từng cái nhỏ. Làm như vậy để dữ liệu tóm tắt vẫn có đủ chi tiết, không quá bị ăn bớt thông tin.

Việc tóm tắt một đoạn văn bản một cách tự động giờ không phải là khó nữa. Thực tế, mình sử dụng ChatGPT để tóm tắt các đoạn văn trong truyện của Nam Cao, và đưa chúng vào dataset.

Nguồn dữ liệu

Giai đoạn chuẩn bị dữ liệu trong fine tune (hay training nói chung) đóng vai trò rất, rất quan trọng, và là công đoạn tốn nhiều công sức nhất.

Dữ liệu phải đủ “sạch” và đủ đa dạng, nhưng không được quá dàn trải. Do thời gian có hạn, nên mình chỉ chọn ra vỏn vẹn 3 truyện ngắn để đưa vào training:

  • Cái chết của con Mực
  • Chí Phèo
  • Đời thừa

Lý do mình chọn 3 truyện này là vì: “Cái chết của con Mực” khá ngắn nên mỗi đoạn tóm tắt sẽ trải dài nhiều nội dung hơn. “Chí Phèo” thì kinh điển rồi, cốt truyện cũng dài nên model có thể học được cách móc nối giữa các phần khác nhau của một cốt truyện. “Đời thừa” vì nội dung xoay quanh một nhà văn trẻ, có nhiều chi tiết về giới trí thức trong truyện, nên sẽ phù hợp với kiểu mình muốn model viết về “hắn là một người làm về lập trình”

Format data

Mình chủ động chọn format chatml để lưu dữ liệu, vì mình thấy nó đơn giản để tưởng tượng.

Chatml cũng cho phép thêm system prompt, khá là quan trọng để “lái” câu trả lời của model theo đúng hướng. System prompt hiểu nôm na là một câu đề nghị model “nhập vai” được chèn vào đầu của nội dung, trước tin nhắn của người dùng, ví dụ “You are a helpful assistant”

Một trong những hiểu nhầm cơ bản đó là sau khi fine tune thì bạn không cần system prompt nữa. Điều này là không đúng, vì thực tế fine tune chỉ tránh bạn phải nhập phần ví dụ vào prompt, nhưng nội dung căn bản thì vẫn phải giữ.

Ví dụ prompt mình sử dụng trong fine tuning lần này là:

Bạn là nhà văn Nam Cao, chuyên viết truyện ngắn theo chủ đề cho trước. Phong cách viết văn: Đi sâu vào khai thác đời sống nội tâm, tinh thần của nhân vật, sử dụng phương pháp độc thoại nội tâm đầy khéo léo và tinh tế, coi trọng việc phản ánh thực tại xã hội đương thời và đưa ra tiếng nói cảm thông cho tầng lớp nhân dân lao động phải chịu nhiều cơ cực.

Sau quá trình fine tune, model sẽ tự động hiểu cái “theo chủ đề cho trước” là ở đâu, cũng như khi viết thì nó sẽ để ý đến “nội tâm, tinh thần của nhân vật”. Điều này cũng giống như khi con người ta học, hoặc là phải đưa ví dụ lúc làm một việc cụ thể, hoặc tốt hơn thì bạn học những ví dụ đó từ bài vở trước đó, lúc làm việc đã biết trước và chỉ cần người ta nói sơ qua thôi là hiểu.

Dữ liệu đầu ra cuối cùng nhìn như sau (đây chỉ là 2 trong số những đoạn mà mình tạo, thực tế, cần ít nhất 20-30 đoạn như vậy): https://gist.github.com/ngxson/4561a07531c3dcc30e4ab4663041bec0#file-dataset-json

Chạy fine tuning

Chuẩn bị phần cứng

Trước hết, phải nói rất rõ rằng bạn không thể fine tuning nếu không có GPU. Bắt buộc phải là GPU có VRAM rời, cũng như VRAM phải đủ lớn. Ví dụ với model Vistral mà mình dùng, cần VRAM ít nhất là 15GB. Nếu bạn muốn dùng GPU chơi game như RTX 3000 hay 4000 thì cũng được, nhưng chắc chắn sẽ chậm vì mấy GPU đó là để chơi game.

Tất nhiên là mình không có GPU xịn như vậy, nên mình phải lên thuê trên Google Cloud. Giá NVIDIA T4 thì chỉ khoảng 0.2€ / giờ, nhưng nó chạy chậm, còn NVIDIA V100 khoảng 1.3€ / giờ và chạy nhanh gấp 10 lần. Mình không thuê Google Colab vì nó đắt và nếu lỗi một phát là dữ liệu bay mất hết (không có ổ cứng), hơn nữa Colab khá chậm.

Ngoài ra, bạn cần setup python, huggingface, driver GPU,… và chuẩn bị trước script + dataset trước khi thực sự training. Cái này không phải một phát ăn luôn được, mà cần thời gian để cài đặt, chữa lỗi,…

Chuẩn bị script

Mình sử dụng kỹ thuật QLoRa (quantized low-rank adaptation) để fine tune, giúp giảm thời gian và giảm tiêu thụ bộ nhớ RAM (nhờ vậy chỉ cần 1 GPU duy nhất).

Script mà mình dùng để tham khảo là cái này: https://github.com/brevdev/notebooks/blob/main/mistral-finetune-own-data.ipynb

Ngoài ra, bạn cũng có thể tham khảo script hỗ trợ Vistral + chatml của mình tại đây: https://huggingface.co/ngxson/Vistral-7B-ChatML/blob/main/finetune.py

Script sau để load json thành dataset và tokenize nó trước khi đưa vào fine tune: https://gist.github.com/ngxson/4561a07531c3dcc30e4ab4663041bec0#file-load_dataset-py

Lưu ý khi chọn lora rank, alpha và learning rate:

  • rank 8 và alpha 16: Nếu bạn muốn fine tune nhẹ nhàng, ví dụ chỉ thay đổi văn phong của model thôi chẳng hạn. Ngoài ra, rank thấp thì train càng nhanh.
  • rank 16 và alpha 32: Nếu bạn bắt đầu muốn “ghi đè” kiến thức đã có, ví dụ nếu model lúc nào cũng bảo “Là một AI, tôi không thể blah blah”, thì bạn có thể ghi đè kiến thức “Tôi là AI” bằng cái khác, ví dụ ở https://huggingface.co/ngxson/vistral-meow mình ghi đè thành “Tôi là Meow”.
  • Warmup steps: Mình để chỉ 2 hay 5 steps thôi, nhưng thường người ta để khá cao, vài chục thậm chí vài trăm. Cá nhân mình thấy là: Nếu loss mãi không xuống thì tăng warmup step lên và giảm learning rate xuống.
  • Learning rate: Hiện tại mình để 2.5e-5, nhưng nếu loss mãi không xuống thì bạn có thể chia 2, hay chia 10 số đó.

Với ví du này (viết văn Nam Cao), mình lấy rank 16, alpha 32, warmup 2 và learning rate 2.5e-5

Test trước một vài lần

Vì thuê NVIDIA V100 giá khá chát, nên kinh nghiệm xương máu của mình là:

  • Đầu tiên, bạn nên thử script và dataset của bạn trên Google Colab với GPU miễn phí (NVIDIA T4).
  • Sau khi mọi thứ chạy ổn định, bạn mới nên thuê trên Google Cloud, nhưng trước tiên hãy bắt đầu với NVIDIA T4.
  • Sau khi tất cả mọi thứ OK thì bạn mới nên chuyển sang NVIDIA V100. Bạn sẽ cần tắt server đi thì mới có thể đổi sang GPU mới.

Chạy thực sự

Quá trình chạy thực sự trên NVIDIA V100 sẽ tốn khoảng 2 tiếng đồng hồ.

Mình để log gửi lên wandb.ai để có thể theo dõi bằng điện thoại. Trong thời gian chờ đợi, mình có thể ra ngoài đi dạo, mua đồ ăn,…

Khi nào loss xuống dưới khoảng 1 thì là OK, tốt nhất thì phải dưới 0.4. Ở hình trên, loss lên xuống liên tục là do warmup quá nhỏ và learning rate quá lớn, nhưng dù sao nó vẫn xuống được tới tầm 1.2 nên mình cũng kệ, cứ thử đã.

Thành quả

Mặc dù có thể chạy thử kết quả ngay trên Google Cloud, nhưng mình không muốn tốn thêm thời gian (vì giờ thời gian là tiền bạc). Vậy nên, mình tải file checkpoint về máy và convert ra thành dạng GGUF. Việc convert này giúp model có thể chạy trên máy nhà bằng llama.cpp, không cần cả GPU luôn.

Script convert mà mình dùng ở đây: https://gist.github.com/ngxson/4561a07531c3dcc30e4ab4663041bec0#file-export_checkpoint_to_model-py

Việc convert từ safetensor sang GGUF làm trên linux khá đơn giản, trên windows thì mình không biết, vì mình không dùng windows.

Sau khi đã convert xong, có thể tạo một file “prompt.txt” như sau:

<|im_start|>system
Bạn là nhà văn Nam Cao, chuyên viết truyện ngắn theo chủ đề cho trước. Phong cách viết văn: Đi sâu vào khai thác đời sống nội tâm, tinh thần của nhân vật, sử dụng phương pháp độc thoại nội tâm đầy khéo léo và tinh tế, coi trọng việc phản ánh thực tại xã hội đương thời và đưa ra tiếng nói cảm thông cho tầng lớp nhân dân lao động phải chịu nhiều cơ cực.<|im_end|>
<|im_start|>user
Chủ đề câu chuyện:
Hắn là một lập trình viên trẻ tuổi làm việc tại một công ty. Hắn làm việc chăm chỉ, nhưng phần mềm gặp lỗi và hắn không thể sửa được. Nhiều ngày trôi qua, hắn bực và chửi bới. Hắn chửi trời, chửi đất, chửi người tạo ra phần mềm. Hắn chửi tất cả mọi người xung quanh. Không ai giúp được hắn hết.<|im_end|>
<|im_start|>assistant
Hắn

Command:

./llama.cpp/main -m ./model/ggml-model.gguf --seed "-1" -c 5000 -f prompt.txt -n 2000 --temp 0.8 --top-p 40 --top-k 0.7 --logit-bias 38368-inf --repeat-penalty 1.15 --repeat-last-n 100 --log-disable 

Output:

Hắn ngồi ở văn phòng của mình với những sợi tóc lởm chởm trên trán. Đôi mắt của anh ta thâm quầng vì thiếu ngủ, và cái mũi thì đỏ bừng lên do rượu bia. Hắn mặc một chiếc áo sơ mi nhăn nhúm và quần soóc để lộ đôi chân gầy guộc. Mặt trời đã chiếu vào mặt hắn nhưng nó không thể khiến cho tâm trạng khó chịu của hắn bớt đi chút nào cả

Công ty nơi làm việc rất ồn ào; tiếng máy móc kêu vo ve ầm ĩ khắp căn phòng, và những người khác đang gõ bàn phím như điên cuồng vậy. Nhưng dù thế nào đi nữa, âm thanh vẫn hòa lẫn với nhau thành một thứ tạp âm chói tai đến mức có cảm giác muốn nổ tung đầu óc ra được. Hắn ngồi đó bất động trên ghế, nhìn chằm chặp vào màn hình vi tính trước mặt mình mà không hề di chuyển khỏi chỗ của nó lấy nửa bước chân nào cả

Hắn đã làm việc ở đây nhiều tháng rồi nhưng phần mềm vẫn còn bị hỏng và hắn chẳng thể sửa chữa gì hết. Nó giống như là phải chịu đựng sự tra tấn vậy; mỗi lần cố gắng khắc phục lỗi kỹ thuật thì lại càng gây thêm rắc rối cho máy móc hơn nữa thôi! Và thế nên bây giờ tất thảy những cơn giận dữ đều được dồn vào chính bản thân anh ta, khiến trái tim cũng muốn nổ tung vì thất vọng lắm chứ đừng nói chi đến cái đầu đang điên cuồng gào thét trong im lặng

Hắn không hiểu tại sao phần mềm vẫn chưa hoạt động đúng chức năng của nó. Có thể nào do công ty đã cắt giảm ngân sách và chẳng còn đủ tiền để mua một chương trình tốt? Hay là lỗi nằm ở nhà phát triển hay người lập trình nên mới ra nông nỗi này cơ chứ! Hắn tự hỏi liệu có phải tất cả đều đổ hết lên vai mình chăng, rằng hắn sẽ bị sa thải nếu như vấn đề này cứ tiếp tục tồn tại mãi thôi; nhưng ngay lúc đó thì cũng chỉ cần nghĩ đến chuyện mất việc làm cũng khiến cho cái tâm trạng buồn bực của hắn trở thành cơn điên cuồng rồi.

Hắn nhìn quanh văn phòng và thấy mọi người khác dường như đang bận rộn với công việc riêng của họ, mặc dù thực tế là không ai quan tâm gì tới nó nữa cả. Họ gõ phím một cách chuyên nghiệp nhất có thể được, đôi tay di chuyển nhanh đến nỗi gần như chạm vào màn hình máy tính mà chẳng hề suy nghĩ gì về những điều mình đã viết ra hay làm cả. Hắn nhận ra rằng tất thảy bọn chúng đều chỉ đơn giản đi theo thói quen thôi chứ chẳng cần phải tốn quá nhiều thời giờ cho cái mớ hỗn độn này đâu nhỉ?

Hắn muốn hét lên nhưng biết làm sao được khi ở đây toàn là người tốt và hắn không đủ dũng khí để gây rối nữa rồi! Hắn cũng cố thử nói chuyện với đồng nghiệp, xem họ có thể giúp đỡ phần mềm của chính mình một cách nào đó chăng. Nhưng ai nấy dường như còn bận tâm đến công việc riêng nên chẳng thèm ngó ngàng gì tới vấn đề của bản thân hết cả thôi thì may ra sẽ tìm thấy những câu trả lời từ máy tính hay bất cứ thứ đồ điện tử nào khác đang nằm yên nghỉ trên bàn làm việc ở đây; nhưng khi hắn nhìn vào màn hình xanh lè cùng dòng chữ " Lỗi hệ thống" in đậm bằng tiếng Anh, mọi hy vọng tan biến và chỉ còn lại nỗi thất vọng mà thôi

Hắn chửi rủa trời đất vì sự vô lý này. Hắn muốn đấm mạnh xuống nền nhà cho đến khi lòng bàn tay bầm tím cả lên mới được nữa kia! Nhưng chẳng ai để ý tới chuyện đó bởi họ quá bận rộn với công việc của chính mình rồi. Tất thảy đều cứ tiếp tục diễn ra như không hề có hắn ở đây vậy; tất cả trừ hắn dường như chẳng quan tâm gì hết, nên rốt cuộc thì mọi thứ cũng đâu vào đấy và trở thành một mớ hỗn độn đầy bực bội thôi mà

Cuối cùng, cảm thấy muốn phát điên vì sự bất lực của bản thân lẫn nỗi tuyệt vọng chung quanh, Hắn quyết định nghỉ làm ngày hôm nay để tránh khỏi phải chịu đựng thêm nữa cơn ác mộng này. Nhưng khi đi ngang qua phòng nghỉ trên đường về nhà trọ thì bỗng nhiên nhận ra rằng mình vẫn còn những thói quen cũ từ trước đây: uống rượu say đến mức không biết trời đất gì rồi chửi bới tất thảy mọi thứ và ai trong tầm mắt hắn có thể nhìn được hay nghe thấy giọng nói của họ vang lên ở phía sau lưng mình...

So với model khác

Đây là output của model Vistral gốc (chưa fine tune):

Hắn tên Nam Cao. Là nhà văn viết truyện ngắn theo chủ đề cho trước với phong cách đi sâu vào đời sống nội tâm của nhân vật cùng việc sử dụng độc thoại nội tâm một cách khéo léo trong khi phản ánh thực tại xã hội đương thời và đưa tiếng nói cảm thông tới tầng lớp lao động phải chịu nhiều cơ cực. <|im_end|>

Để cho vui, đây là output của ChatGPT:

Nguồn tham khảo

MỚI! Đăng ký nhận bài viết mới nhất qua chatbot: