Chuyển đến nội dung chính

Gemma 3 270M: Hướng dẫn kèm dự án demo (Ứng dụng Android)

Tìm hiểu cách thiết lập, tích hợp và chạy mô hình Gemma 3 270M trong ứng dụng Android, cùng các phương pháp hay nhất để đảm bảo độ tin cậy và an toàn cho suy luận ngay trên thiết bị.
· 12 phút đọc

Khám phá cùng AI

Gần đây Google đã ra mắt Gemma 3 270M, một mô hình siêu gọn với 270 triệu tham số, đại diện cho một mô hình triển khai AI hiệu quả kiểu mới. Khác với các mô hình tổng quát khổng lồ, Gemma 3 270M được thiết kế để tinh chỉnh theo tác vụ cụ thể nhưng vẫn duy trì khả năng làm theo hướng dẫn ngay khi sử dụng.

Trong hướng dẫn này, tôi sẽ chỉ bạn cách bắt đầu với Gemma 3 270M và lý do nó là lựa chọn tuyệt vời cho các nhà phát triển cần:

  • Suy luận cực nhanh với chi phí tính toán tối thiểu
  • Hiệu suất năng lượng vượt trội (chỉ dùng 0,75% pin cho 25 cuộc hội thoại trên Pixel 9 Pro)
  • Lượng tử hóa với độ chính xác INT4 và hao hụt hiệu năng tối thiểu
  • Riêng tư tuyệt đối với khả năng chạy hoàn toàn trên thiết bị

Chúng tôi luôn cập nhật cho độc giả những tin tức AI mới nhất qua The Median, bản tin miễn phí mỗi thứ Sáu tóm lược các câu chuyện quan trọng trong tuần. Đăng ký để luôn nắm bắt chỉ trong vài phút mỗi tuần:

Gemma 3 270M là gì?

Gemma 3 270M là một mô hình nền tảng gọn nhẹ, được thiết kế để tạo văn bản có cấu trúc và làm theo hướng dẫn tốt, với chỉ 270 triệu tham số. Đây là thành viên nhỏ nhất trong họ Gemma 3, nhưng thừa hưởng cùng kiến trúc và công thức huấn luyện như các biến thể lớn hơn như Gemma 3 1B và 4B.

Khác với các mô hình lớn đa dụng, Gemma 3 270M tập trung vào hiệu quả và chuyên biệt hóa. Dưới đây là vài điểm then chốt về mô hình này:

  • Chạy nhẹ nhàng trên thiết bị di động; suy luận đã lượng tử (INT4, Q8, v.v.) tiêu tốn dưới 1% pin cho nhiều cuộc hội thoại.
  • Có 100M tham số transformer và ma trận nhúng lớn 170M, hỗ trợ bộ từ vựng 256.000 token, lý tưởng để xử lý token hiếm trong các ứng dụng theo lĩnh vực.
  • Các checkpoint tinh chỉnh theo hướng dẫn cũng có sẵn, giúp dễ dàng xây dựng trợ lý, bộ trích xuất dữ liệu và công cụ sáng tác không cần kết nối đám mây.

Hiệu năng Gemma 3 270M trên IFEval

Gemma 3 270M có khả năng làm theo hướng dẫn tốt — benchmark IFEval kiểm tra khả năng của mô hình trong việc làm theo thông tin có thể kiểm chứng. Nguồn: Google

Với cửa sổ ngữ cảnh 32K, dùng bộ nhớ thấp và độ chính xác vững vàng ở các tác vụ như trích xuất thực thể, phân loại và kiểm tra tuân thủ nhẹ, Gemma 3 270M rất phù hợp cho nhà phát triển edge AI tìm kiếm sự riêng tư, tốc độ và hiệu quả chi phí.

Cách truy cập Gemma 3 270M?

Có nhiều cách đơn giản để bắt đầu với Gemma 3 270M, tùy nền tảng bạn ưa thích:

1. Cloud Run

Bạn có thể triển khai Gemma 3 trên Google Cloud Run bằng container dựng sẵn tăng tốc GPU cho suy luận có thể mở rộng. Với Gemma 3 270M, bạn cần tự dựng container từ repo Gemma-on-CloudRun trên GitHub, vì chỉ các biến thể lớn (1B+) mới có bản dựng sẵn. Hãy làm theo hướng dẫn trong tài liệu chính thức của Google Cloud Run để bật suy luận nhanh, an toàn và tiết kiệm cho ứng dụng web hoặc backend.

2. Android (Khuyến nghị)

Trải nghiệm người dùng tốt nhất là chạy trên thiết bị, dùng bản lượng tử của Gemma 3 270M (gemma3-270m-it-q8) và ứng dụng Gemma Gallery chính thức từ Google. Cách này mang lại suy luận ngoại tuyến cực nhanh, độ riêng tư cao và tiêu thụ pin tối thiểu.

3. Suy luận cục bộ

Bạn cũng có thể chạy Gemma 3 270M trên laptop bằng các công cụ như:

  • LM Studio: Tải mô hình với phần mở rộng .gguf hoặc .litertlm trong môi trường LM Studio và chạy ngay trong giao diện chat hoặc host cục bộ.
  • Ollama: Bạn cũng có thể dùng lệnh ollama run gemma3:270m để chạy mô hình này trên máy của bạn.
  • Llama.cpp: Cũng cung cấp mô hình Gemma 3 270M cho suy luận CPU siêu nhẹ.
  • Docker: Bạn có thể chạy Gemma 3 bằng container Docker, từ Hugging Face hoặc repo GitHub chính thức của Gemma.
  • HuggingFace: Người dùng có thể truy cập trực tiếp hoặc suy luận trên mô hình qua giao diện HuggingFace, hoặc tải các mô hình pretrained và tinh chỉnh theo hướng dẫn để dùng cục bộ.

4. Tinh chỉnh mô hình

Gemma 3 270M cũng có sẵn trong Hugging Face Transformers, JAX hoặc Unsloth để tinh chỉnh trên dữ liệu theo lĩnh vực của bạn. Kích thước nhỏ khiến mô hình lý tưởng cho thử nghiệm nhanh, chi phí thấp, và các checkpoint QAT cho phép triển khai INT4 trực tiếp sau tinh chỉnh.

Tiếp theo, tôi sẽ hướng dẫn bạn xây dựng một ứng dụng Android hoạt động sử dụng mô hình Gemma 3 270M để xử lý prompt văn bản. Chúng ta sẽ dùng kho Gallery chính thức của Google làm nền và tùy biến.

Bước 1: Clone và thiết lập ứng dụng

Hãy bắt đầu bằng cách thiết lập dự án mới và clone repository gốc từ Google.

Bước 1.1: Tạo dự án Android Studio mới

Trên laptop của bạn, mở dự án như một dự án mới trong Android Studio và chọn Empty Activity.

Cửa sổ New Project của Android Studio để chạy gemma 3 270M

Bước 1.2: Thiết lập activity trống mới

Điền tên activity của bạn (ví dụ: “Gemma 3 270M”) và giữ nguyên các phần còn lại. Sau đó nhấn Finish.

Bước 1.3: Clone repository chính

Bây giờ, mở terminal trong Android Studio (góc dưới bên trái) và chạy các lệnh bash sau:

git clone https://github.com/google-ai-edge/gallery
cd gallery/android

Terminal trong Android Studio để chạy gemma 3 270M

Dự án sẽ được mở. Bạn có thể xem tất cả tệp dự án ở phía bên trái của tab.

Các tệp trong Android Studio

Bước 2: Kết nối mô hình

Để kết nối mô hình Gemma 3 270M với ứng dụng Android của bạn, bạn cần đăng ký nó như một tác vụ mới trong tệp Tasks.kt của ứng dụng.

Đi tới thư mục data/ và mở tệp Tasks.kt. Ở đó, bạn sẽ định nghĩa một mục tác vụ mới dành riêng cho biến thể lượng tử Q8 của mô hình gemma3-270m-it-q8, được tối ưu cho bộ nhớ thấp và suy luận trên thiết bị. Định dạng này phù hợp cho thiết bị di động có RAM hạn chế.

Vị trí tệp Tasks.kt

Lưu ý: Trong hướng dẫn này, tôi sử dụng cấu hình mô hình Q8, cần ít bộ nhớ hơn. Bạn có thể truy cập mô hình tại: https://huggingface.co/litert-community/gemma-3-270m-it

val TASK_GEMMA_3_270M = Task(
  type = TaskType.LLM_PROMPT_LAB,
  icon = Icons.Outlined.Widgets,
  models = mutableListOf(
    Model(
      name = "gemma3-270m-it-q8",
      downloadFileName = "gemma3-270m-it-q8.task",
      url = "https://huggingface.co/litert-community/gemma-3-270m-it/resolve/main/gemma3-270m-it-q8.task",
      sizeInBytes = 318_767_104L
    )
  ),
  description = "Gemma 3 270M (Q8): On-device instruction-following LLM",
  docUrl = "https://ai.google.dev/gemma",
  sourceCodeUrl = "https://github.com/google-ai-edge/gallery",
  textInputPlaceHolderRes = R.string.chat_textinput_placeholder
)

Sau đó, đăng ký tác vụ mới này trong ứng dụng bằng cách đưa vào danh sách tác vụ chính:

/** All tasks */
val TASKS: List<Task> = listOf(
  TASK_LLM_ASK_IMAGE,
  TASK_LLM_CHAT,
  TASK_LLM_PROMPT_LAB,
  TASK_IMAGE_CLASSIFICATION,
  TASK_TEXT_CLASSIFICATION,
  TASK_IMAGE_GENERATION,
  TASK_GEMMA_3_270M
)

Dưới đây là phần giải thích những gì định nghĩa tác vụ LiteRT mới này làm trong mã:

  • Định nghĩa tác vụ mới: Biến TASK_GEMMA_3_270M đăng ký Gemma 3 270M như một tùy chọn có thể chọn trong tệp Tasks.kt để hiển thị trong danh sách tác vụ.
  • Liên kết tệp .task: Đoạn mã trên trỏ tới gói gemma3-270m-it-q8.task được host trên Hugging Face, sẽ được tải xuống trong ứng dụng và dùng khi chạy.
  • Chi tiết mô hình: Tệp .task chứa trọng số mô hình đã lượng tử, tokenizer, metadata và cấu hình cần thiết để LiteRT chạy mô hình hiệu quả.
  • Biến thể Q8: Lượng tử hóa này cân bằng tiết kiệm bộ nhớ với chất lượng đầu ra tốt, đủ nhẹ cho điện thoại nhưng vẫn xử lý tốt các yêu cầu làm theo hướng dẫn.
  • Metadata tác vụ: Thêm mô tả, URL tài liệu và liên kết mã nguồn, giúp dễ hiểu và bảo trì tác vụ trong ứng dụng Gallery.
  • Danh sách tác vụ: Cuối cùng đảm bảo mô hình được nhận diện trong danh sách tác vụ bằng cách liệt kê tác vụ mô hình là TASK_GEMMA_3_270M.

Bước 3: Sửa lỗi nạp danh sách mô hình

Theo mặc định, ứng dụng Gallery nạp danh sách mô hình từ một JSON allowlist từ xa. Nếu mục Gemma 3 270M tùy chỉnh của bạn không có trong đó, nó sẽ không hiển thị trên UI, ngay cả khi bạn đã thêm TASK_GEMMA_3_270M trong mã. Bước này sẽ ghép mô hình 270M của bạn vào tác vụ LLM Prompt Lab sau khi allowlist được nạp, đảm bảo luôn có thể chọn.

Mở modelmanager/ModelManagerViewModel.kt, tìm hàm loadModelAllowlist(), và (sau vòng lặp chuyển đổi các mô hình trong allowlist) nối thêm:

Log.d(TAG, "Adding TASK_GEMMA_3_270M models to LLM_PROMPT_LAB")
            for (gemmaModel in TASK_GEMMA_3_270M.models) {
              if (!TASK_LLM_PROMPT_LAB.models.any { it.name == gemmaModel.name }) {
                Log.d(TAG, "Adding model ${gemmaModel.name} to LLM_PROMPT_LAB")
                TASK_LLM_PROMPT_LAB.models.add(gemmaModel)
              }

Lý do cần đoạn mã này:

  • Đặt lại allowlist: Ứng dụng xóa và dựng lại TASK_LLM_PROMPT_LAB.models từ allowlist máy chủ khi khởi động. Mô hình cục bộ của bạn sẽ bị xóa trừ khi bạn chèn lại.
  • Bất biến và chống trùng lặp: Bộ bảo vệ any { it.name == gemmaModel.name } ngăn trùng lặp khi hàm loadModelAllowlist() chạy lại.
  • Thân thiện ngoại tuyến: Mã này hoạt động dù allowlist nạp từ mạng hay từ bộ nhớ đệm trên đĩa. Mô hình đều được nối thêm trong cả hai trường hợp.

Đây là hàm hoàn chỉnh:

fun loadModelAllowlist() {
    _uiState.update {
      uiState.value.copy(
        loadingModelAllowlist = true, loadingModelAllowlistError = ""
      )
    }
    viewModelScope.launch(Dispatchers.IO) {
      try {
        withTimeoutOrNull(30000L) { // 30 second timeout
          // Load model allowlist json.
          Log.d(TAG, "Loading model allowlist from internet...")
          val data = getJsonResponse<ModelAllowlist>(url = MODEL_ALLOWLIST_URL)
          var modelAllowlist: ModelAllowlist? = data?.jsonObj
          if (modelAllowlist == null) {
            Log.d(TAG, "Failed to load model allowlist from internet. Trying to load it from disk")
            modelAllowlist = readModelAllowlistFromDisk()
          } else {
            Log.d(TAG, "Done: loading model allowlist from internet")
            saveModelAllowlistToDisk(modelAllowlistContent = data?.textContent ?: "{}")
          }
          if (modelAllowlist == null) {
            Log.e(TAG, "Failed to load model allowlist from both internet and disk")
            _uiState.update { 
              uiState.value.copy(
                loadingModelAllowlist = false,
                loadingModelAllowlistError = "Failed to load model list"
              ) 
            }
            return@withTimeoutOrNull
          }
          Log.d(TAG, "Allowlist: $modelAllowlist")
          // Convert models in the allowlist.
          TASK_LLM_CHAT.models.clear()
          TASK_LLM_PROMPT_LAB.models.clear()
          TASK_LLM_ASK_IMAGE.models.clear()          
          try {
            for (allowedModel in modelAllowlist.models) {
              if (allowedModel.disabled == true) {
                continue
              }
              val model = allowedModel.toModel()
              if (allowedModel.taskTypes.contains(TASK_LLM_CHAT.type.id)) {
                TASK_LLM_CHAT.models.add(model)
              }
              if (allowedModel.taskTypes.contains(TASK_LLM_PROMPT_LAB.type.id)) {
                TASK_LLM_PROMPT_LAB.models.add(model)
              }
              if (allowedModel.taskTypes.contains(TASK_LLM_ASK_IMAGE.type.id)) {
                TASK_LLM_ASK_IMAGE.models.add(model)
              }
            }
            // Add models from TASK_GEMMA_3_270M to LLM_PROMPT_LAB
            Log.d(TAG, "Adding TASK_GEMMA_3_270M models to LLM_PROMPT_LAB")
            for (gemmaModel in TASK_GEMMA_3_270M.models) {
              if (!TASK_LLM_PROMPT_LAB.models.any { it.name == gemmaModel.name }) {
                Log.d(TAG, "Adding model ${gemmaModel.name} to LLM_PROMPT_LAB")
                TASK_LLM_PROMPT_LAB.models.add(gemmaModel)
              }
            }
          } catch (e: Exception) {
            Log.e(TAG, "Error processing model allowlist", e)
          }
          // Pre-process all tasks.
          try {
            processTasks()
          } catch (e: Exception) {
            Log.e(TAG, "Error processing tasks", e)
          }
          // Update UI state.
          val newUiState = createUiState()
          _uiState.update {
            newUiState.copy(
              loadingModelAllowlist = false,
            )
          }
          // Process pending downloads.
          try {
            processPendingDownloads()
          } catch (e: Exception) {
            Log.e(TAG, "Error processing pending downloads", e)
          }
        } ?: run {
          // Timeout occurred
          Log.e(TAG, "Model allowlist loading timed out")
          _uiState.update { 
            uiState.value.copy(
              loadingModelAllowlist = false,
              loadingModelAllowlistError = "Model list loading timed out"
            ) 
          }
        }
      } catch (e: Exception) {
        Log.e(TAG, "Error in loadModelAllowlist", e)
        _uiState.update { 
          uiState.value.copy(
            loadingModelAllowlist = false,
            loadingModelAllowlistError = "Failed to load model list: ${e.message}"
          ) 
        }
      }
    }
  }

Bước 4: Cập nhật logic điều hướng

Mặc định, ứng dụng giả định tác vụ hiện được chọn khớp với mô hình vừa nhấn. Khi bạn chèn Gemma 3 270M vào tác vụ Prompt Lab (Bước 3), giả định đó có thể sai. Bước này giúp điều hướng nhận biết mô hình, để thao tác chạm, deep link và trạng thái khôi phục luôn đến đúng màn hình.

Mở navigation/GalleryNavGraph.kt và cập nhật điều khiển điều hướng theo mô hình như sau:

Trước:

onModelClicked = { model ->
  navigateToTaskScreen(
    navController = navController, taskType = curPickedTask.type, model = model
  )
}

Sau:

onModelClicked = { model ->
  Log.d("GalleryNavGraph", "Model clicked: ${model.name}, current task: ${curPickedTask.type}")
  val actualTask = when {
    TASK_LLM_CHAT.models.any { it.name == model.name } -> TASK_LLM_CHAT
    TASK_LLM_PROMPT_LAB.models.any { it.name == model.name } -> TASK_LLM_PROMPT_LAB
    TASK_LLM_ASK_IMAGE.models.any { it.name == model.name } -> TASK_LLM_ASK_IMAGE
    TASK_TEXT_CLASSIFICATION.models.any { it.name == model.name } -> TASK_TEXT_CLASSIFICATION
    TASK_IMAGE_CLASSIFICATION.models.any { it.name == model.name } -> TASK_IMAGE_CLASSIFICATION
    TASK_IMAGE_GENERATION.models.any { it.name == model.name } -> TASK_IMAGE_GENERATION
    else -> curPickedTask
  }
  Log.d("GalleryNavGraph", "Navigating to task: ${actualTask.type} for model: ${model.name}")
  navigateToTaskScreen(
    navController = navController, taskType = actualTask.type, model = model
  )
}

Nếu một mô hình xuất hiện trong (hoặc được chèn vào) tác vụ khác với tác vụ đang chọn, đoạn mã này đảm bảo màn hình khớp với khả năng của mô hình. 

Khi xử lý deep link hoặc trạng thái khôi phục, ứng dụng có thể cố mở một mô hình không còn khả dụng. Nếu không xử lý đúng, điều này sẽ dẫn tới màn hình trống. Để tránh điều đó, chúng ta thêm kiểm tra an toàn: nếu thiếu mô hình, ghi log cảnh báo và điều hướng lại người dùng. Cách làm như sau:

// Added error handling for model not found:
if (model != null) {
  Log.d("GalleryNavGraph", "Model found for LlmChat: ${model.name}")
  modelManagerViewModel.selectModel(model)
  LlmChatScreen(/* ... */)
} else {
  Log.w("GalleryNavGraph", "Model not found for LlmChat, modelName: ${it.arguments?.getString("modelName")}")
  // Handle case where model is not found
  androidx.compose.runtime.LaunchedEffect(Unit) {
    navController.navigateUp()
  }
}

Đoạn mã này đảm bảo ứng dụng vẫn hoạt động. Thay vì đơ hoặc hiển thị màn hình trống, ứng dụng sẽ điều hướng ngược an toàn bằng navController. Nhờ vậy, ngay cả khi thiếu mô hình, luồng người dùng vẫn mượt và không vỡ trải nghiệm tổng thể.

Bước 5: An toàn và độ tin cậy 

Chạy mô hình ngôn ngữ lớn trên thiết bị di động khác với trên máy tính để bàn. Lưu trữ có thể không ổn định, ngân sách bộ nhớ chặt chẽ hơn và lỗi khởi tạo cần được xử lý để tránh crash. Để Gemma 3 270M (Q8, ~304MB) ổn định trên Android, tôi đã bổ sung kiểm tra an toàn và chiến lược dự phòng.

Bước 5.1: Xác thực tệp với dung sai kích thước

Trước khi nạp mô hình, ứng dụng thực hiện kiểm tra hợp lý trên tệp .task. Điều này đảm bảo tệp tồn tại, có thể đọc và gần với kích thước kỳ vọng (trong khoảng ±10%). Mức dung sai này tính đến sai khác do nén hoặc đóng gói, đồng thời phát hiện tải xuống hỏng hoặc chưa đầy đủ.

// File validation and logging
Log.d(TAG, "Checking model file: $modelPath")
Log.d(TAG, "Model file exists: ${modelFile.exists()}")
Log.d(TAG, "Model file can read: ${modelFile.canRead()}")
Log.d(TAG, "Model file size: ${modelFile.length()} bytes")
Log.d(TAG, "Model file absolute path: ${modelFile.absolutePath}")
// Size validation with tolerance (10%)
val expectedSize = model.sizeInBytes
val fileSize = modelFile.length()
val sizeDifference = kotlin.math.abs(fileSize - expectedSize)
val sizeTolerance = (expectedSize * 0.1).toLong()
if (!modelFile.exists() || !modelFile.canRead() || sizeDifference > sizeTolerance) {
  onDone("Model file invalid (missing/unreadable/wrong size).")
  return
}

Đoạn mã này xác thực tệp mô hình trước khi nạp. Nó ghi log các chi tiết cơ bản như đường dẫn, khả năng đọc và kích thước, rồi kiểm tra sự tồn tại, khả năng đọc và mức khớp kích thước. Nếu bất kỳ kiểm tra nào không đạt, hàm sẽ dừng sớm qua phương thức onDone() thay vì cố suy luận và có nguy cơ crash.

Bước 5.2: Chọn backend theo bộ nhớ 

Các mô hình lớn có thể gây lỗi hết bộ nhớ (OOM) nếu khởi chạy “mạnh tay” (đầu ra dài, đột biến phân bổ GPU). Để giảm thiểu, tôi thêm logic chọn backend an toàn nhất (CPU so với GPU/NPU) dựa trên kích thước tệp và cấu hình thiết bị, đồng thời cắt bớt số token tối đa cho các tệp nhị phân quá lớn.

val preferredBackend = when {
  fileSize > 200_000_000L -> {
    Log.d(TAG, "Model is large (${fileSize / 1024 / 1024}MB), using CPU backend for safety")
    LlmInference.Backend.CPU
  }
  accelerator == Accelerator.CPU.label -> LlmInference.Backend.CPU
  accelerator == Accelerator.GPU.label -> LlmInference.Backend.GPU
  else -> LlmInference.Backend.CPU
}
val baseMaxTokens = 1024 // your default; tune to taste
val maxTokens = if (fileSize > 200_000_000L) {
  kotlin.math.min(baseMaxTokens, 512)
} else {
  baseMaxTokens
}

Cách này đảm bảo mô hình lớn mặc định dùng CPU (an toàn hơn nhưng chậm hơn). Mã kiểm tra kích thước byte của tệp .task (fileSize) và so với ngưỡng (maxTokens). Nếu vượt 200 MB, nó chuyển suy luận sang CPU và giảm một nửa ngân sách token. Điều này giúp tránh đột biến bộ nhớ (do phân bổ GPU đồng thời) và yêu cầu sinh đầu ra quá lớn.

Bước 5.3: Xử lý ngoại lệ

Khi khởi tạo LiteRT, cần phân biệt giữa tạo runtime và tạo session. Nếu tạo session thất bại nhưng bạn vẫn để runtime mở, có thể rò rỉ tài nguyên và khóa bộ nhớ, gây lỗi dây chuyền. Để khắc phục, tôi bao bọc mã trong các khối try/catch kèm dọn dẹp rõ ràng.

val llmInference = try {
  LlmInference.createFromOptions(context, options)
} catch (e: Exception) {
  Log.e(TAG, "Failed to create LlmInference instance", e)
  onDone("Failed to create model instance: ${e.message}")
  return
}
val session = try {
  LlmInferenceSession.createFromOptions(/* ... */)
} catch (e: Exception) {
  Log.e(TAG, "Failed to create LlmInferenceSession", e)
  try {
    llmInference.close()
  } catch (closeException: Exception) {
    Log.e(TAG, "Failed to close LlmInference after session creation failure", closeException)
  }
  onDone("Failed to create model session: ${e.message}")
  return
}

Khối try/catch này đảm bảo rollback sạch: nếu tạo runtime thất bại, không thực hiện gì thêm; nếu tạo session thất bại, runtime được đóng tường minh. Điều này đảm bảo đối tượng llmInference cấp phát trên heap được giải phóng qua .close(), trả bộ nhớ và handle tệp. Nếu không, bạn có nguy cơ gặp tham chiếu JNI gây crash ở lần nạp mô hình tiếp theo.

Bước 5.4: Bảo vệ khi suy luận

Ngay cả khi mô hình nạp thành công, suy luận có thể thất bại do điều kiện tranh chấp, tham chiếu null hoặc instance chưa khởi tạo. Để tăng độ vững, tôi thêm kiểm tra null và bắt ngoại lệ quanh toàn bộ khối suy luận.

try {
  Log.d(TAG, "Starting inference for model '${model.name}' with input: '${input.take(50)}...'")
  val instance = model.instance as LlmModelInstance? ?: run {
    Log.e(TAG, "Model instance is null for '${model.name}'")
    resultListener("Error: Model not initialized", true)
    return
  }
} catch (e: Exception) {
  Log.e(TAG, "Error during inference for model '${model.name}'", e)
  resultListener("Error during inference: ${e.message}", true)
}

Đoạn mã trên đảm bảo chỉ tiến hành suy luận khi có instance mô hình hợp lệ và bắt lỗi runtime một cách gọn gàng. Ép kiểu as LlmModelInstance? kiểm tra instance đúng kiểu và nhánh fallback dạng Elvis rút ngắn nếu null. Bao khối trong try/catch đảm bảo mọi ngoại lệ đều được bắt và phản hồi bằng thông báo lỗi hiển thị cho người dùng.

Bước 6: Cài đặt và chạy ứng dụng trên thiết bị

Sau khi đã thực hiện các thay đổi cần thiết, chúng ta sẽ chạy ứng dụng. Để chạy ứng dụng trên thiết bị Android cục bộ, hãy ghép đôi thiết bị với Android Studio bằng gỡ lỗi không dây theo các bước sau.

Bước 6.1: Thiết lập thiết bị Android

Vào ứng dụng Cài đặt trên thiết bị Android và bật Tùy chọn nhà phát triển. Sau đó bật Gỡ lỗi không dây. Bạn sẽ thấy màn hình với hai tùy chọn ghép đôi.

Cài đặt gỡ lỗi không dây trên Android để chạy gemma 3 270M

Bước 6.2: Quản lý thiết bị trong Android Studio

Trong Android Studio, mở Device Manager (biểu tượng giống chiếc điện thoại với logo Android) và nhấn Pair using QR code.

Ghép đôi thiết bị mới qua WiFi

Quét mã QR bằng thiết bị của bạn và hoàn tất ghép đôi. Khi thiết bị được kết nối, bạn sẽ thấy tên thiết bị trong Device Manager. Đảm bảo máy tính và thiết bị Android cùng kết nối một mạng WiFi.

Device Manager

Bước 6.3: Chạy ứng dụng

Bắt đầu phản chiếu màn hình thiết bị Android bằng cách nhấn biểu tượng Device Mirroring (minh họa màn hình desktop và điện thoại). Khi thấy màn hình thiết bị được phản chiếu, nhấn biểu tượng xanh Run để triển khai ứng dụng lên thiết bị.

Bước 6.4: Tải mô hình về thiết bị

Ứng dụng sẽ khởi chạy và một cửa sổ trình duyệt mở ra, nhắc bạn đăng nhập Hugging Face bằng token write để tải tệp mô hình an toàn.

Sau khi đăng nhập HuggingFace, nhấn start download trên mô hình bạn chọn, và mô hình sẽ bắt đầu tải xuống. Trong ví dụ này, tôi dùng mặc định mô hình gemma3-270m-it-q8. Giờ bạn có thể bắt đầu thử nghiệm với mô hình này.

Bước 7: Bắt đầu thử nghiệm

Trong khuôn khổ hướng dẫn, tôi đã chạy bốn thí nghiệm:

Thí nghiệm 1: Viết tự do

Ở thí nghiệm này, tôi yêu cầu mô hình viết một câu chuyện tự do, và đây là kết quả:

Mô hình tạo văn bản đúng chủ đề với cấu trúc rõ ràng, dù có xu hướng “an toàn” và hơi chung chung. Với đầu ra dài, mô hình thường lặp lại một số câu. Vì thế nó phù hợp nhất cho bài ngắn, mô tả và dàn ý. Tuy nhiên, thời gian tạo token đầu tiên khá nhanh khoảng 0,53 giây, và tốc độ giải mã cũng tốt cho suy luận trên thiết bị.

Thí nghiệm 2: Viết lại giọng điệu

Tiếp theo, tôi dùng một đoạn văn viết sẵn và yêu cầu Gemma 3 270M viết lại đoạn văn theo giọng điệu thân thiện.

Thí nghiệm 2: Viết lại giọng điệu

Khả năng làm theo hướng dẫn mạnh, chuyển đổi rõ giữa trang trọng, thân mật và thân thiện trong khi vẫn giữ nguyên ý. Tốc độ gần như tức thì với đoạn ngắn, rất phù hợp để trau chuốt email hoặc chuyển đổi phong cách văn bản.

Thí nghiệm 3: Tóm tắt văn bản

Tôi cũng tóm tắt đoạn văn sẵn có thành các gạch đầu dòng. Bạn có thể thử nghiệm với tóm tắt, điểm chính, điều rút ra, v.v.

Thí nghiệm 3: Tóm tắt văn bản

Trong thí nghiệm này, mô hình tóm tắt và nêu bật các ý chính. Tốc độ xuất sắc với tài liệu ngắn và trung bình. Một số trường hợp sử dụng là tóm tắt điều hành, điểm nổi bật dạng bullet, hoặc TL;DR. 

Thí nghiệm 4: Viết đoạn mã

Cuối cùng, tôi yêu cầu mô hình viết một đoạn mã nhỏ. Đây là những gì tôi nhận thấy:

Thí nghiệm 4: Viết đoạn mã

Mô hình hiệu quả khi tạo boilerplate và tiện ích nhỏ, nhưng có thể thiếu import, case biên hoặc logic phức tạp nếu không có hướng dẫn. Đoạn ngắn sinh nhanh, còn phần hoàn thiện dài chậm hơn đôi chút khi ngữ cảnh tăng. Phù hợp nhất cho hàm trợ giúp và cấu hình. 

Tôi đã thiết lập repository GitHub này nếu bạn muốn khám phá toàn bộ mã dự án.

Kết luận

Trong hướng dẫn này, chúng ta đã đi từng bước để chạy mô hình Gemma 3 270M, từ tải xuống và kết nối mô hình vào ứng dụng Android tùy chỉnh, đến xử lý điều hướng, độ tin cậy và an toàn khi suy luận.

Gemma 3 mang lại suy luận nhanh với chi phí tính toán thấp, tiêu thụ rất ít điện năng, hỗ trợ lượng tử hóa INT4 với tác động nhỏ đến hiệu năng, và chạy hoàn toàn trên thiết bị để duy trì quyền riêng tư.

Aashi Dutt

Tác giả

Aashi Dutt

Tôi là Chuyên gia Google Developers trong lĩnh vực ML (Gen AI), Chuyên gia Kaggle 3x và Đại sứ Women Techmakers với hơn 3 năm kinh nghiệm trong ngành công nghệ. Tôi đồng sáng lập một startup công nghệ y tế vào năm 2020 và hiện đang theo học thạc sĩ khoa học máy tính tại Georgia Tech, chuyên sâu về học máy.

Chủ đề

Liên quan