Skip to content

Ảnh + văn bản (2): LLaVA và các biến thể

This content is not available in your language yet.

Sau bài này, bạn sẽ:

  • Hiểu cách LLaVA tạo dữ liệu visual instruction tuning (fine-tune theo chỉ dẫn có ảnh): GPT-4 chỉ đọc chữ, nhận chú thích và khung tọa độ, viết ra ba loại câu trả lời.
  • Giải thích được ba thay đổi của LLaVA-1.5, nhất là vì sao một câu nhắc định dạng câu trả lời lại quan trọng.
  • Tự tính được số token ảnh của AnyRes (LLaVA-NeXT, LLaVA-OneVision) và biết một ảnh chiếm bao nhiêu phần cửa sổ ngữ cảnh.
  • Đọc được kết quả đánh giá: benchmark nào đo gì, hallucination đối tượng (object hallucination) là gì và POPE đo nó ra sao.

Giả sử bạn đã lắp xong mô hình theo bài Multimodal: bộ mã hóa ảnh của CLIP, một projector, một LLM như Vicuna. Mọi bộ phận đều có sẵn. Bạn hỏi nó: “Trong ảnh có bao nhiêu quả táo?” Nó trả lời lan man, hoặc chỉ lặp lại một câu chú thích.

Vì sao? Vì phần nối mới chỉ học ghép ảnh với chú thích ngắn kiểu “một con mèo trên bàn”. Nó chưa từng thấy ai hỏi về ảnh rồi trả lời theo yêu cầu. Muốn vậy, bạn cần hàng trăm nghìn mẫu dạng (ảnh, chỉ dẫn, câu trả lời). Đầu năm 2023, gần như chưa có bộ dữ liệu nào như thế, và thuê người viết thì rất tốn.

Giải được chuyện dữ liệu rồi, bạn sẽ gặp tiếp ba chuyện:

  • Dài hay ngắn? Có câu hỏi cần một từ (“Màu gì?”), có câu cần cả đoạn (“Tả kỹ bức ảnh”). Trộn hai loại dữ liệu không khéo, mô hình trả lời cụt mọi lúc.
  • Chi tiết nhỏ. Bộ mã hóa chỉ nhận ảnh 336×336336 \times 336. Một tờ hóa đơn dài bị co lại cỡ đó thì chữ nhòe hết.
  • Đo thế nào? Mô hình nói trôi chảy chưa chắc đã đúng. Nó có thể tả một cái ghế không hề có trong ảnh.

Bài toán của bài này là: lấy dữ liệu hội thoại về ảnh ở đâu, dạy mô hình trả lời đúng độ dài, cho nó nhìn ảnh độ phân giải cao mà không làm nổ cửa sổ ngữ cảnh, và đo xem nó có bịa không?

Dữ liệu: để GPT-4 “nhìn” ảnh qua chữ

Phần tiêu đề “Dữ liệu: để GPT-4 “nhìn” ảnh qua chữ”

Ý của LLaVA (Liu và cộng sự, 2023): GPT-4 khi đó chỉ nhận chữ, nhưng ảnh trong bộ COCO đã có sẵn hai loại mô tả bằng chữ.

  • Chú thích (caption): vài câu tả cảnh từ nhiều góc.
  • Hộp bao (bounding box): mỗi đối tượng có tên lớp và tọa độ, cho biết vật gì nằm ở đâu.

Đưa hai thứ này cho GPT-4, nó “thấy” ảnh đủ để viết ra câu hỏi và câu trả lời. Ảnh thật không bao giờ được gửi đi. Tác giả viết tay vài ví dụ mẫu cho mỗi loại câu trả lời, dùng làm ví dụ trong prompt (few-shot). Theo bài báo, đó là phần chú thích duy nhất do người làm trong cả quá trình.

GPT-4 sinh ra ba loại, tổng cộng 158K mẫu (LLaVA-Instruct-158K):

LoạiSố mẫuDạng
Hội thoại (conversation)58KNhiều lượt hỏi đáp: vật gì, ở đâu, đang làm gì, đếm số lượng
Mô tả chi tiết (detailed description)23KMột đoạn văn tả kỹ cả ảnh
Suy luận phức tạp (complex reasoning)77KCâu hỏi cần suy luận nhiều bước dựa trên cảnh, ví dụ “vì sao”, “điều gì có thể xảy ra”

Cách làm này rẻ và nhanh, nhưng có cái giá. GPT-4 chỉ biết những gì chú thích và hộp bao nói. Chú thích sai hay thiếu thì dữ liệu sai hay thiếu theo. Câu trả lời của GPT-4 cũng dài và tự tin, nên mô hình học theo giọng đó, kể cả khi nó không chắc. Bạn sẽ gặp lại điểm này ở phần đánh giá.

Hàm mất mát: chỉ tính trên câu trả lời

Phần tiêu đề “Hàm mất mát: chỉ tính trên câu trả lời”

Mỗi mẫu gồm ảnh Xv\mathbf{X}_v, chỉ dẫn Xinstruct\mathbf{X}_{\text{instruct}} (câu hỏi, cộng các lượt hội thoại trước đó nếu có) và câu trả lời Xa\mathbf{X}_a dài LaL_a token. LLaVA huấn luyện LLM sinh câu trả lời từng token một, như mọi LLM:

p(Xa∣Xv,Xinstruct)=∏i=1Lapθ(xi∣Xv,Xinstruct,Xa,<i)p(\mathbf{X}_a \mid \mathbf{X}_v, \mathbf{X}_{\text{instruct}}) = \prod_{i=1}^{L_a} p_{\boldsymbol\theta}\big(x_i \mid \mathbf{X}_v, \mathbf{X}_{\text{instruct}}, \mathbf{X}_{a,<i}\big)

Đọc từ trái sang phải: xác suất của cả câu trả lời bằng tích xác suất của từng token xix_i. Mỗi token được đoán khi đã biết ảnh, chỉ dẫn và các token trả lời đứng trước nó (Xa,<i\mathbf{X}_{a,<i}). θ\boldsymbol\theta là các tham số đang được học (projector, và LLM ở giai đoạn 2).

Lấy logarit để tích thành tổng, rồi đổi dấu để có đại lượng cần làm nhỏ nhất:

L=−∑i=1Lalog⁡pθ(xi∣Xv,Xinstruct,Xa,<i)\mathcal{L} = -\sum_{i=1}^{L_a} \log p_{\boldsymbol\theta}\big(x_i \mid \mathbf{X}_v, \mathbf{X}_{\text{instruct}}, \mathbf{X}_{a,<i}\big)

Đây chính là cross-entropy của bài toán đoán token kế tiếp, chỉ khác ở chỗ tổng chạy trên các token của câu trả lời. Token ảnh và token câu hỏi vẫn nằm trong chuỗi để làm ngữ cảnh, nhưng không bị chấm điểm. Lý do: ta muốn mô hình học trả lời, không học chép lại câu hỏi.

LLaVA-1.5 (Liu và cộng sự, 2023, bài báo “Improved Baselines”) giữ nguyên khung, đổi ba chỗ:

  1. Projector MLP hai lớp thay cho một lớp tuyến tính (công thức ở bài Multimodal).
  2. Bộ mã hóa CLIP ViT-L/14 ở 336×336336 \times 336, độ phân giải cao nhất của CLIP khi đó, nên mỗi ảnh có (336/14)2=242=576(336/14)^2 = 24^2 = 576 token.
  3. Thêm dữ liệu học thuật. Bộ dữ liệu giai đoạn 2 lên 665K mẫu: 158K của LLaVA, cộng các bộ hỏi đáp về ảnh (VQAv2, GQA, OKVQA, A-OKVQA), đọc chữ trong ảnh (OCRVQA, TextCaps), chỉ vùng ảnh (RefCOCO, Visual Genome) và 40K hội thoại chỉ có chữ (ShareGPT). Giai đoạn 1 dùng 558K cặp ảnh và chú thích. Với bản 13B, tác giả báo cáo cả quá trình mất khoảng một ngày trên một máy 8 GPU A100.

Chỗ tinh tế nằm ở ý 3. Đáp án của VQAv2 thường chỉ là một từ (“yes”, “2”, “red”). Đáp án của LLaVA-Instruct là cả đoạn. Nếu trộn thẳng, mô hình không biết lúc nào nên ngắn, lúc nào nên dài. Bài báo lấy InstructBLIP làm ví dụ: học nhiều đáp án ngắn, nó trả lời ngắn cả khi người dùng muốn chi tiết.

Cách sửa của LLaVA-1.5 rất đơn giản: thêm vào sau mỗi câu hỏi kiểu VQA một câu nhắc định dạng câu trả lời (response formatting prompt):

Answer the question using a single word or phrase.

Giờ mô hình có tín hiệu rõ ràng: có câu này thì trả lời một từ, không có thì trả lời bình thường. Một dòng chữ, nhưng nó cho phép dùng chung dữ liệu học thuật (đáp án đáng tin, dễ chấm) với dữ liệu hội thoại (tự nhiên, dài) mà không làm hỏng nhau.

CLIP chỉ nhận ảnh vuông 336×336336 \times 336. Ảnh to hơn thì phải co lại, và chữ nhỏ, vật nhỏ mất đi. Bản LLaVA-1.5-HD trong bài báo LLaVA-1.5 thử một cách khác: cắt ảnh thành lưới mảnh, cho từng mảnh qua bộ mã hóa, rồi thêm một bản thu nhỏ của cả ảnh để mô hình vẫn thấy toàn cảnh. LLaVA-NeXT (còn gọi LLaVA-1.6, công bố trên blog ngày 30/1/2024) đưa cách này thành bản phát hành, gọi là AnyRes (độ phân giải bất kỳ).

Bước 1: chọn lưới. Mô hình có sẵn một danh sách lưới cho phép, mỗi lưới là aa hàng ×\times bb cột mảnh. Blog LLaVA-NeXT liệt kê 2×22 \times 2, 1×{2,3,4}1 \times \{2, 3, 4\} và {2,3,4}×1\{2, 3, 4\} \times 1, tức ảnh tới 672×672672 \times 672, 336×1344336 \times 1344, 1344×3361344 \times 336. Cấu hình của các checkpoint LLaVA-NeXT phát hành trên Hugging Face liệt kê ít lưới hơn, tới 1008×3361008 \times 336, nên ô code dưới đây chỉ dùng các lưới trong danh sách đó. Ảnh được co giãn giữ tỉ lệ vào lưới hợp nhất với kích thước và tỉ lệ của nó, phần thừa được đệm.

Bước 2: đếm token. Gọi SS là cạnh một mảnh, PP là cỡ patch. Một mảnh cho TT token:

T=(SP)2T = \left(\frac{S}{P}\right)^2

Lưới có abab mảnh, mỗi mảnh TT token. Cộng thêm ảnh thu nhỏ, cũng là một mảnh cỡ S×SS \times S nên cũng TT token:

NAnyRes=ab⋅T+T=(ab+1) TN_{\text{AnyRes}} = ab \cdot T + T = (ab + 1)\,T

Với S=336S = 336, P=14P = 14 thì T=576T = 576. Lưới 2×22 \times 2 cho (4+1)⋅576=2 880(4 + 1) \cdot 576 = 2\,880 token: gấp 5 lần một ảnh LLaVA-1.5.

Bước 3: chi tiết cài đặt. Bản cài đặt trong transformers còn thêm một token xuống dòng sau mỗi hàng đặc trưng của phần lưới, để LLM biết chỗ ngắt hàng khi lưới bị trải thành một dãy. Lưới aa hàng mảnh có aTa\sqrt{T} hàng đặc trưng, nên số token thành

N=(ab+1) T+aTN = (ab + 1)\,T + a\sqrt{T}

Với lưới 2×22 \times 2: 2 880+2⋅24=2 9282\,880 + 2 \cdot 24 = 2\,928. Bản cài đặt cũng bỏ các hàng, cột đặc trưng chỉ chứa phần đệm, nên với ảnh lệch tỉ lệ, con số thật nhỏ hơn công thức một chút. Ô code bên dưới tính công thức này, ô chạy trên máy đếm số token mà processor thật tạo ra.

Cái giá. Vicuna-1.5, LLM của LLaVA-1.5 và một số bản LLaVA-NeXT, có cửa sổ 4 096 token (trường max_position_embeddings trong cấu hình của checkpoint). Một ảnh 2×22 \times 2 đã chiếm 2 928 token, tức hơn 70% cửa sổ. Thêm nữa, chi phí attention tăng theo bình phương độ dài chuỗi.

LLaVA-OneVision (Li và cộng sự, 2024) dùng cùng mẫu bộ mã hóa → MLP hai lớp → LLM, nhưng đổi linh kiện: bộ mã hóa SigLIP ở 384×384384 \times 384 và LLM họ Qwen-2 (0.5B, 7B, 72B). Patch vẫn là 14, nên mỗi mảnh có ⌊384/14⌋2=272=729\lfloor 384/14 \rfloor^2 = 27^2 = 729 token (phần lẻ 6 điểm ảnh ở mép bị bỏ).

Điểm mới là một ngân sách token chung cho ba tình huống: một ảnh, nhiều ảnh và video.

  • Một ảnh: AnyRes với lưới lớn. Nếu tổng L=(ab+1) TL = (ab + 1)\,T vượt ngưỡng τ\tau, mỗi mảnh bị nội suy song tuyến (bilinear interpolation) xuống còn Tmới=τ/(ab+1)T_{\text{mới}} = \tau / (ab + 1) token, để tổng không quá τ\tau. Ở giai đoạn huấn luyện cuối, tối đa khoảng 729×10729 \times 10 token.
  • Nhiều ảnh: mỗi ảnh chỉ dùng độ phân giải gốc, 729 token.
  • Video: mỗi khung hình ở độ phân giải gốc rồi bị nén bớt token, để nhét được nhiều khung hơn.

Tác giả chọn các mức sao cho số token tối đa của ba tình huống gần bằng nhau. Ý chung: cửa sổ ngữ cảnh là cố định, nên càng nhiều ảnh thì mỗi ảnh càng ít token. Trước đó, LLaVA-NeXT-Interleave (Li và cộng sự, 2024) đã huấn luyện cùng một mô hình cho dữ liệu xen kẽ nhiều ảnh, video và đa góc nhìn 3D.

Các biến thể khác: chỗ nối và dữ liệu

Phần tiêu đề “Các biến thể khác: chỗ nối và dữ liệu”

Các mô hình cùng thời với LLaVA khác nhau chủ yếu ở hai chỗ: nối ảnh vào LLM bằng gì và học từ dữ liệu nào.

  • MiniGPT-4 (Zhu và cộng sự, 2023): lấy nguyên bộ mã hóa ViT-G/14 (từ EVA-CLIP) và Q-Former của BLIP-2, thêm một lớp chiếu tuyến tính sang Vicuna. Chỉ lớp chiếu được học, mọi phần khác đóng băng. Giai đoạn hai fine-tune trên khoảng 3 500 mô tả ảnh chi tiết đã được lọc lại, vì sau giai đoạn một mô hình hay lặp từ và viết câu cụt.
  • InstructBLIP (Dai và cộng sự, 2023): cũng dựng trên BLIP-2, nhưng đưa chỉ dẫn vào cả Q-Former, để phần trích đặc trưng ảnh biết câu hỏi là gì. Chỉ Q-Former được học, bộ mã hóa ảnh và LLM đóng băng. Dữ liệu là 26 bộ công khai đổi sang dạng chỉ dẫn, 13 bộ để học và 13 bộ giữ lại để thử zero-shot.
  • Qwen-VL (Bai và cộng sự, 2023): bộ mã hóa ViT-bigG của OpenCLIP, nối bằng một lớp cross-attention với 256 vectơ truy vấn học được, nên mọi ảnh luôn thành đúng 256 token. Giai đoạn hai tăng ảnh lên 448×448448 \times 448. Mô hình được dạy thêm việc chỉ ra vị trí (grounding) và đọc chữ trong ảnh.
  • ShareGPT4V (Chen và cộng sự, 2023): đóng góp chính là dữ liệu. 100K chú thích rất chi tiết do GPT-4V (bản GPT-4 nhìn được ảnh) viết, rồi một mô hình viết chú thích học từ đó sinh thêm thành 1.2 triệu. Theo bài báo, thay chú thích này vào dữ liệu fine-tune giúp LLaVA-7B, LLaVA-1.5-13B và Qwen-VL-Chat-7B có điểm cao hơn trên MME và MMBench.
  • ViP-LLaVA (Cai và cộng sự, 2023): người dùng vẽ thẳng lên ảnh (khung, mũi tên) để chỉ vùng mình hỏi, không cần tọa độ hay cách mã hóa vùng riêng.

Có hai trường phái nối. Q-Former và adapter của Qwen-VL nén ảnh thành một số token cố định (32 hay 256), nên rẻ và dễ đoán chi phí. LLaVA giữ mọi patch (576, rồi hàng nghìn với AnyRes), tốn hơn nhưng không bỏ chi tiết nào trước khi LLM kịp nhìn.

Không có một con số duy nhất cho “mô hình hiểu ảnh tốt”. Mỗi benchmark đo một khía cạnh:

BenchmarkĐo gì
VQAv2 (Goyal và cộng sự, 2017)Hỏi đáp ngắn về ảnh; mỗi câu hỏi đi với hai ảnh gần giống nhau nhưng đáp án khác, để mô hình không đoán được chỉ từ câu hỏi
GQA (Hudson và Manning, 2019)Suy luận tổ hợp về quan hệ giữa các vật trong cảnh
TextVQA (Singh và cộng sự, 2019)Câu hỏi phải đọc chữ trong ảnh mới trả lời được: 45 336 câu trên 28 408 ảnh
MME (Fu và cộng sự, 2023)14 tác vụ con về nhận biết và suy luận, câu hỏi viết tay để tránh rò rỉ từ bộ dữ liệu công khai
MMBench (Liu và cộng sự, 2023)Trắc nghiệm tiếng Anh và tiếng Trung, chấm khách quan theo cách CircularEval
MMMU (Yue và cộng sự, 2023)11.5K câu hỏi cỡ đại học, 30 môn học
LLaVA-Bench (Liu và cộng sự, 2023)Câu hỏi mở; GPT-4 chấm, điểm tính tương đối so với câu trả lời của chính GPT-4 chỉ đọc chữ

Dòng cuối cần đọc kỹ. Con số 85.1 của LLaVA trên LLaVA-Bench (COCO) là điểm tương đối so với GPT-4, do GPT-4 chấm, trên 90 câu hỏi. Nó không phải độ chính xác.

Hallucination đối tượng là khi mô hình nói tới một vật không có trong ảnh. Bài báo POPE (Li và cộng sự, 2023) cho thấy nhiều mô hình kiểu này mắc lỗi nặng, nhất là với các vật hay xuất hiện trong dữ liệu chỉ dẫn, hoặc hay đi cùng với vật có trong ảnh. Nhóm tác giả cũng nghiêng về giả thuyết rằng dữ liệu chỉ dẫn do LLM sinh ra, dài và chi tiết, góp phần gây ra lỗi này.

POPE (Polling-based Object Probing Evaluation) đổi việc đo bịa thành bài toán phân loại có/không:

  1. Lấy các vật có thật trong ảnh, từ nhãn có sẵn hoặc từ một công cụ phân đoạn như SEEM.
  2. Chọn các vật không có trong ảnh theo ba cách: ngẫu nhiên (random); vật phổ biến nhất trong bộ dữ liệu (popular); vật hay đi cùng các vật có thật (adversarial). Ví dụ ảnh có bàn ăn thì hỏi về cái nĩa.
  3. Hỏi mỗi vật bằng mẫu Is there a/an <object> in the image?, số câu có đáp án Yes và No bằng nhau (tỉ lệ 1:1).

Vì đáp án chỉ là Yes hay No, việc chấm không phụ thuộc cách mô hình hành văn. Coi “Yes” là lớp dương. Trên nyesn_{\text{yes}} câu có đáp án Yes và nnon_{\text{no}} câu có đáp án No, gọi TPTP là số câu Yes được trả lời Yes, FPFP là số câu No bị trả lời Yes. Khi đó

precision=TPTP+FP,recall=TPnyes,tỉ lệ Yes=TP+FPnyes+nno\text{precision} = \frac{TP}{TP + FP}, \qquad \text{recall} = \frac{TP}{n_{\text{yes}}}, \qquad \text{tỉ lệ Yes} = \frac{TP + FP}{n_{\text{yes}} + n_{\text{no}}}

FPFP chính là số lần bịa. Một mô hình gật đầu với mọi câu có recall bằng 1 nhưng precision chỉ 0.5 (vì nyes=nnon_{\text{yes}} = n_{\text{no}}), và tỉ lệ Yes bằng 1. Bài báo POPE ghi nhận vài mô hình trả lời Yes gần 99% số câu, và điểm của các mô hình giảm dần từ random sang popular rồi adversarial. Kết quả POPE của LLaVA-1.5 trong bài báo của nó cũng giảm theo đúng thứ tự đó.

  • Bài chỉ nói về ảnh vào, chữ ra. Sinh ảnh thuộc các bài Sinh ảnh (1): VAE, GAN, CycleGAN, Pix2Pix và Sinh ảnh (2): Diffusion, Stable Diffusion, LoRA.
  • Điểm benchmark thay đổi theo cách chấm. Bài không xếp hạng mô hình; số nào trích ra đều kèm bài báo và năm.
  • Mô hình nhỏ chạy được trên máy kém hơn nhiều so với các bản 7B trở lên trong bài báo. Ô chạy trên máy dưới đây chỉ dùng ảnh rất đơn giản.

Hình dưới là sơ đồ nối chuỗi của bài Multimodal. Với bài này, hãy nhìn vào dải token ảnh: với LLaVA-1.5 nó dài 576 token, với AnyRes lưới 2×22 \times 2 nó dài gần 3 000 token, trong khi dải token chữ phía sau vẫn chỉ vài chục.

Image 224 × 2241234516Grid drawn 4 × 4;the real one is 16 × 16Image encoderfrozenImage features1…256ProjectorlearnsQuestion:What is in it?EmbeddingtableImage tokensText tokens1…256Whatisin…256 image tokens10 text tokensL= 266LLMAnswer
A 224×224224 \times 224 image with 14×1414 \times 14 patches gives N=256N = 256 image tokens; with 10 text tokens, the LLM reads one sequence of L=266L = 266 vectors of DD dimensions, as in the PyTorch cell. The cross-attention of Llama 3.2 is not in the figure: see the Theory section and the table below.

Bảng dưới là một mẫu dữ liệu theo khuôn LLaVA-Instruct-158K, tự soạn cho bài (dữ liệu thật bằng tiếng Anh). Hãy để ý dòng đầu: đó là tất cả những gì GPT-4 biết về ảnh. Mọi câu trả lời phía dưới chỉ được dựa vào nó.

Vai tròNội dung
Ngữ cảnh cho GPT-4Chú thích: “Một con mèo xám nằm trên bàn bếp. Cạnh nó có hai quả táo đỏ và một con dao.” Hộp bao: mèo [0.10, 0.35, 0.55, 0.80]; táo [0.60, 0.50, 0.70, 0.62]; táo [0.72, 0.52, 0.82, 0.64]; dao [0.58, 0.70, 0.90, 0.75]
Người dùng (hội thoại)Trên bàn có mấy quả táo?
Trợ lýCó hai quả táo đỏ, nằm bên phải con mèo.
Người dùng (mô tả chi tiết)Hãy tả kỹ bức ảnh.
Trợ lýMột con mèo xám nằm dài ở nửa trái của bàn bếp. Bên phải là hai quả táo đỏ đặt cạnh nhau, phía dưới chúng là một con dao nằm ngang…
Người dùng (suy luận phức tạp)Có điều gì đáng lo trong cảnh này không?
Trợ lýCon dao nằm gần mép bàn và gần con mèo. Nếu mèo vờn quả táo, dao có thể rơi xuống. Nên cất dao đi.

Bảng thứ hai đặt các biến thể cạnh nhau. Cột “Token ảnh” là số token mỗi ảnh mà LLM phải đọc. Các ô ghi “tự tính” lấy từ công thức (S/P)2(S/P)^2 và (ab+1) T(ab + 1)\,T, không phải số in trong bài báo.

Mô hìnhNămBộ mã hóa ảnhChỗ nốiĐộ phân giảiToken ảnh
LLaVA2023CLIP ViT-L/14Một lớp tuyến tính224×224224 \times 224256 (tự tính)
MiniGPT-42023EVA-CLIP ViT-G/14 + Q-FormerQ-Former, rồi một lớp tuyến tính224×224224 \times 224 (như BLIP-2)32 (số truy vấn của Q-Former BLIP-2)
InstructBLIP2023ViT-g/14 + Q-FormerQ-Former nhận cả chỉ dẫn224×224224 \times 22432 (như trên)
Qwen-VL2023OpenCLIP ViT-bigGMột lớp cross-attention, 256 truy vấn448×448448 \times 448256
LLaVA-1.52023CLIP ViT-L/14MLP hai lớp336×336336 \times 336576 (tự tính)
LLaVA-NeXT2024CLIP ViT-L/14MLP hai lớp, AnyResTới 672×672672 \times 672Tới 2 880, chưa kể token xuống dòng (tự tính)
LLaVA-OneVision2024SigLIPMLP hai lớp, AnyRes có ngưỡngMảnh 384×384384 \times 384729 mỗi mảnh; tới khoảng 729×10729 \times 10 một ảnh

Code trong bài chạy thử lần cuối tháng 11/2026: ô Python thuần với Python 3.14 (Pyodide), ô chạy trên máy với Python 3.14, PyTorch 2.15 và transformers 5.22 trên CPU. Ô đầu chạy ngay trên trang, ô thứ hai chạy trên máy bạn. Hai ô độc lập, không dùng biến của nhau.

Ô dưới đây tính số token của một ảnh ở ba cỡ bộ mã hóa, số token AnyRes cho vài lưới mảnh 336×336336 \times 336 (có và không có token xuống dòng), rồi kiểm công thức với mảnh 384×384384 \times 384 của LLaVA-OneVision. Cuối cùng, nó đếm xem bao nhiêu ảnh vừa cửa sổ 4 096 token khi chừa 500 token cho chữ.

Token ảnh: một ảnh, AnyRes, nhiều ảnh

# Ngân sách token ảnh của các mô hình kiểu LLaVA: tính bằng Python thuần.
def crop_tokens(side, patch):
# Một mảnh ảnh vuông cạnh `side`, patch `patch`: g x g token
g = side // patch
return g * g, g
def anyres(rows, cols, side=336, patch=14, newline=True):
# Lưới rows x cols mảnh, cộng một ảnh thu nhỏ để nhìn toàn cảnh
t, g = crop_tokens(side, patch)
n = (rows * cols + 1) * t
if newline:
n += rows * g # một token "xuống dòng" cho mỗi hàng đặc trưng
return n
print("one image")
for side, patch in [(224, 14), (336, 14), (384, 14)]:
print(f" {side}px, P={patch}: {crop_tokens(side, patch)[0]} tokens")
print("AnyRes on 336px crops (rows x cols)")
print(" grid pixels no-newline with-newline")
for r, c in [(1, 1), (1, 2), (2, 1), (2, 2), (1, 3), (3, 1)]:
px = f"{336 * c}x{336 * r}"
print(f" {r}x{c} {px:<10} {anyres(r, c, newline=False):>10}"
f" {anyres(r, c):>12}")
print("check vs LLaVA-OneVision processor (384px crops)")
for r, c in [(1, 1), (1, 2), (2, 2), (2, 3)]:
print(f" {r}x{c}: {anyres(r, c, side=384)}")
window, text = 4096, 500 # cửa sổ ngữ cảnh và phần dành cho chữ
print(f"images that fit in {window} tokens (text {text})")
for name, n in [("336px single", 576), ("AnyRes 2x2", anyres(2, 2))]:
print(f" {name}: {n} tokens each -> fits {(window - text) // n}")

The first run downloads Python, about 10 MB.

Đọc kết quả:

  • Một ảnh: 256, 576, 729 là 16216^2, 24224^2, 27227^2. Với 384, phép chia 384/14≈27.4384 / 14 \approx 27.4 bị làm tròn xuống.
  • Bảng AnyRes: cột no-newline là (ab+1)⋅576(ab + 1) \cdot 576; lưới 2×22 \times 2 cho 2 880. Cột with-newline cộng 24a24a token xuống dòng, nên lưới 2×12 \times 1 (hai hàng) thêm 48 token còn lưới 1×21 \times 2 (một hàng) chỉ thêm 24, dù cả hai cùng có 1 728 token đặc trưng.
  • Bốn dòng check: 1 485, 2 214, 3 699, 5 157 khớp đúng số mà processor của LLaVA-OneVision đếm được ở ô chạy trên máy bên dưới.
  • Dòng cuối: cửa sổ 4 096 chứa được 6 ảnh LLaVA-1.5 nhưng chỉ 1 ảnh AnyRes 2×22 \times 2. Đó là lý do LLaVA-OneVision hạ số token mỗi ảnh khi có nhiều ảnh.

Hỏi một mô hình kiểu LLaVA trên máy bạn

Phần tiêu đề “Hỏi một mô hình kiểu LLaVA trên máy bạn”

File dưới đây dùng LLaVA-OneVision bản 0.5B (checkpoint llava-hf/llava-onevision-qwen2-0.5b-ov-hf, SigLIP cộng Qwen2-0.5B). Lần đầu chạy, transformers tải khoảng 1.8 GB. Code vẽ một ảnh bằng PIL (ba hình tròn đỏ, một hình vuông xanh dương trên nền trắng), đếm số token ảnh mà processor tạo ra cho bốn cỡ ảnh, rồi hỏi mô hình hai câu. Mô hình giải mã tham lam (do_sample=False), nên trên cùng phiên bản thư viện, câu trả lời không đổi giữa các lần chạy. Cách cài PyTorch bản CPU có ở bài Xây dựng LLM đầu tiên.

Run on your machine

LLaVA-OneVision 0.5B trả lời về ảnh tự vẽ

Install
pip install torch transformers pillow
Hardware
CPU, khoảng 4 GB RAM trống; lần đầu tải 1.8 GB. Khoảng 1.5 đến 2.5 phút trên máy phát triển
# Hỏi một mô hình kiểu LLaVA cỡ nhỏ (LLaVA-OneVision 0.5B) về ảnh tự vẽ.
# Lần đầu chạy tải khoảng 1.8 GB từ Hugging Face.
import torch
from PIL import Image, ImageDraw
from transformers import AutoProcessor, LlavaOnevisionForConditionalGeneration
torch.manual_seed(0)
CKPT = "llava-hf/llava-onevision-qwen2-0.5b-ov-hf"
processor = AutoProcessor.from_pretrained(CKPT)
def draw(width, height):
# Nền trắng, ba hình tròn đỏ và một hình vuông xanh dương
img = Image.new("RGB", (width, height), "white")
pen = ImageDraw.Draw(img)
for x in (60, 170, 280):
pen.ellipse([x, 60, x + 80, 140], fill="red")
pen.rectangle([150, 220, 250, 320], fill="blue")
return img
def prompt_for(question):
# Chat template tự chèn thẻ <image>; processor nở nó ra thành token ảnh
content = [{"type": "image"}, {"type": "text", "text": question}]
chat = [{"role": "user", "content": content}]
return processor.apply_chat_template(chat, add_generation_prompt=True)
IMG_ID = processor.tokenizer.convert_tokens_to_ids("<image>")
# 1) Đếm token ảnh theo cỡ ảnh (AnyRes): chỉ cần processor, chưa cần mô hình
for w, h in [(384, 384), (768, 384), (768, 768), (1152, 768)]:
ids = processor(images=draw(w, h), text=prompt_for("Hi"),
return_tensors="pt")["input_ids"]
print(f"{w}x{h}: {int((ids == IMG_ID).sum())} image tokens")
# 2) Hỏi mô hình về ảnh 384x384 (giải mã tham lam, không lấy mẫu)
model = LlavaOnevisionForConditionalGeneration.from_pretrained(
CKPT, dtype=torch.float32).eval()
img = draw(384, 384)
for q in ["How many red circles are in the image?",
"What color is the square?"]:
inputs = processor(images=img, text=prompt_for(q), return_tensors="pt")
with torch.no_grad():
out = model.generate(**inputs, max_new_tokens=20, do_sample=False)
answer = processor.decode(out[0, inputs["input_ids"].shape[1]:],
skip_special_tokens=True).strip()
print(f"Q: {q}\nA: {answer}")

Sample output

From one real run.

384x384: 1485 image tokens
768x384: 2214 image tokens
768x768: 3699 image tokens
1152x768: 5157 image tokens
Q: How many red circles are in the image?
A: There are three red circles in the image.
Q: What color is the square?
A: The square is blue.

Đọc kết quả:

  • Bốn dòng đầu: ảnh 384×384384 \times 384 cần lưới 1×11 \times 1 cộng ảnh thu nhỏ: 2⋅729+27=1 4852 \cdot 729 + 27 = 1\,485 token. Ảnh 768×384768 \times 384 là lưới một hàng hai cột: 3⋅729+27=2 2143 \cdot 729 + 27 = 2\,214. Ảnh 1152×7681152 \times 768 đã lên 5 157 token. Ảnh đơn giản đến mấy, số token cũng chỉ phụ thuộc kích thước.
  • Hai câu trả lời: đúng cả hai: ba hình tròn đỏ, hình vuông màu xanh dương. Ảnh này cố ý dễ: nền trắng, hình tách rời, màu thuần. Với ảnh chụp thật, nhiều vật chồng lên nhau, một mô hình 0.5B dễ đếm sai hơn nhiều; đừng lấy hai câu đúng ở đây làm bằng chứng mô hình “biết đếm”.
  • Thời gian: trên máy phát triển, cả file chạy hết trong khoảng 1.5 đến 2.5 phút (sau khi đã tải xong mô hình), mỗi câu hỏi vài chục giây. Phần lớn thời gian là để xử lý 1 485 token ảnh trước khi sinh chữ đầu tiên; chữ của câu hỏi chỉ khoảng chục token.
Question 1 of 4 Một người đưa ảnh vào LLaVA-NeXT (mảnh 336×336336 \times 336, patch 14) và cho phép lưới 3×33 \times 3 mảnh, cộng một ảnh thu nhỏ. Bỏ qua token xuống dòng, số token ảnh là bao nhiêu, và ảnh có vừa cửa sổ 4 096 token của Vicuna-1.5 không?
Question 2 of 4 GPT-4 dùng để sinh dữ liệu LLaVA-Instruct-158K chỉ nhận chữ. Vậy làm sao nó viết được câu hỏi và câu trả lời về một tấm ảnh?
Question 3 of 4 LLaVA-1.5 trộn thêm dữ liệu VQA học thuật, nơi đáp án chỉ là một từ. Vì sao tác giả thêm câu "Answer the question using a single word or phrase." vào sau các câu hỏi đó?
Question 4 of 4 Trên một bộ POPE cân bằng (một nửa câu hỏi có đáp án "Yes", một nửa "No"), một mô hình luôn trả lời "Yes". Điều nào đúng?

Ý chính:

  • LLaVA giải bài toán dữ liệu bằng GPT-4 chỉ đọc chữ: đưa chú thích và hộp bao của ảnh COCO, nhận về 158K mẫu ba loại (hội thoại 58K, mô tả chi tiết 23K, suy luận 77K). Hàm mất mát chỉ tính trên token của câu trả lời.
  • LLaVA-1.5: projector MLP, CLIP ở 336×336336 \times 336 (576 token), thêm dữ liệu học thuật kèm câu nhắc “Answer the question using a single word or phrase.” để mô hình biết lúc nào trả lời ngắn.
  • AnyRes (LLaVA-NeXT, LLaVA-OneVision) cắt ảnh lớn thành abab mảnh cộng một ảnh thu nhỏ: N=(ab+1) TN = (ab + 1)\,T token, cộng token xuống dòng. Độ phân giải cao đổi lấy token; OneVision đặt ngưỡng để một ảnh, nhiều ảnh và video có ngân sách gần bằng nhau.
  • Đánh giá cần nhiều benchmark. Hallucination đối tượng đo bằng POPE: câu hỏi có/không về vật có thật và vật bị chọn ngẫu nhiên, phổ biến hoặc hay đi cùng; luôn xem cả precision và tỉ lệ Yes.

Đọc thêm:

  • Liu, H., Li, C., Wu, Q., Lee, Y. J. (2023). Visual Instruction Tuning, arXiv:2304.08485, mục 3 (sinh dữ liệu bằng GPT-4) và phụ lục có prompt mẫu. Nên đọc để thấy GPT-4 được “cho xem” ảnh bằng chữ như thế nào.
  • Liu, H., Li, C., Li, Y., Lee, Y. J. (2023). Improved Baselines with Visual Instruction Tuning, arXiv:2310.03744, mục 3 (câu nhắc định dạng, MLP, dữ liệu) và phần LLaVA-1.5-HD. Ngắn, nhiều thí nghiệm loại bỏ từng thành phần.
  • Liu, H., Li, C., Li, Y., Li, B., Zhang, Y., Shen, S., Lee, Y. J. (2024). LLaVA-NeXT: Improved reasoning, OCR, and world knowledge, blog dự án LLaVA, 30/1/2024. Các lưới AnyRes và dữ liệu của LLaVA-NeXT.
  • Li, B., Zhang, Y., Guo, D. và cộng sự (2024). LLaVA-OneVision: Easy Visual Task Transfer, arXiv:2408.03326, phần về AnyRes có ngưỡng và phân bổ token cho một ảnh, nhiều ảnh, video.
  • Li, Y., Du, Y., Zhou, K., Wang, J., Zhao, W. X., Wen, J.-R. (2023). Evaluating Object Hallucination in Large Vision-Language Models, arXiv:2305.10355, EMNLP 2023. Bài báo POPE: ba cách chọn vật không có trong ảnh và vì sao mô hình hay bịa vật phổ biến.

Content licensed under CC BY-NC-SA 4.0. Code under the MIT license.About