From 7ed79319e591d1fca2cd1d79b23a9497ada97ea3 Mon Sep 17 00:00:00 2001 From: Han Yin Date: Sat, 19 Apr 2025 21:13:11 -0700 Subject: [PATCH] GGUF: make GgufMetadata serializable in order to be compatible with Room --- .../llama/revamp/data/local/ModelEntity.kt | 7 +++- .../llama/revamp/data/model/ModelInfo.kt | 2 ++ .../revamp/data/repository/ModelRepository.kt | 10 ++---- .../llama/revamp/ui/components/ModelCards.kt | 36 +------------------ .../ui/screens/ModelsManagementScreen.kt | 4 +-- .../example/llama/revamp/util/GgufMetadata.kt | 27 ++++++++++++++ .../viewmodel/ModelsManagementViewModel.kt | 6 ++-- 7 files changed, 45 insertions(+), 47 deletions(-) diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/local/ModelEntity.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/local/ModelEntity.kt index e78097b406..60e6a60105 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/local/ModelEntity.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/local/ModelEntity.kt @@ -2,9 +2,11 @@ package com.example.llama.revamp.data.local import androidx.room.Entity import androidx.room.PrimaryKey +import androidx.room.TypeConverters import com.example.llama.revamp.data.model.ModelInfo +import com.example.llama.revamp.util.GgufMetadata +import com.example.llama.revamp.util.GgufMetadataConverters -// TODO-han.yin: Add GgufMetaData @Entity(tableName = "models") data class ModelEntity( @@ -13,6 +15,8 @@ data class ModelEntity( val name: String, val path: String, val sizeInBytes: Long, + @field:TypeConverters(GgufMetadataConverters::class) + val metadata: GgufMetadata, val dateAdded: Long, val lastUsed: Long? ) { @@ -21,6 +25,7 @@ data class ModelEntity( name = name, path = path, sizeInBytes = sizeInBytes, + metadata = metadata, lastUsed = lastUsed, ) } diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/model/ModelInfo.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/model/ModelInfo.kt index c01f21cd65..9eb368f24a 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/model/ModelInfo.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/model/ModelInfo.kt @@ -1,5 +1,6 @@ package com.example.llama.revamp.data.model +import com.example.llama.revamp.util.GgufMetadata import com.example.llama.revamp.util.formatSize /** @@ -10,6 +11,7 @@ data class ModelInfo( val name: String, val path: String, val sizeInBytes: Long, + val metadata: GgufMetadata, val lastUsed: Long? = null ) { val formattedSize: String diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/repository/ModelRepository.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/repository/ModelRepository.kt index aecebc73e2..1a96f77920 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/repository/ModelRepository.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/data/repository/ModelRepository.kt @@ -192,8 +192,8 @@ class ModelRepositoryImpl @Inject constructor( Log.i(TAG, "Extracting GGUF Metadata from $filePath") GgufMetadataReader().readStructuredMetadata(filePath) } catch (e: Exception) { - Log.e(TAG, "Failed to extract GGUF metadata: ${e.message}", e) - null + Log.e(TAG, "Cannot extract GGUF metadata: ${e.message}", e) + throw e } // Create model entity and save via DAO @@ -202,15 +202,11 @@ class ModelRepositoryImpl @Inject constructor( name = fileName.substringBeforeLast('.'), path = modelFile.absolutePath, sizeInBytes = modelFile.length(), - // TODO-han.yin: add metadata here + metadata = metadata, dateAdded = System.currentTimeMillis(), lastUsed = null ).let { modelDao.insertModel(it) - - importJob = null - currentModelFile = null - it.toModelInfo() } diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/components/ModelCards.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/components/ModelCards.kt index 8d0c1d45b0..0f9ef5e427 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/components/ModelCards.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/components/ModelCards.kt @@ -174,41 +174,7 @@ private fun ModelInfoContent( Spacer(modifier = Modifier.height(4.dp)) - // Model details row (parameters, quantization, size) - Row { - if (model.parameters != null) { - Text( - text = model.parameters, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - - if (model.quantization != null) { - Text( - text = " • ${model.quantization}", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - - Text( - text = " • ${model.formattedSize}", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - - Spacer(modifier = Modifier.height(4.dp)) - - // Context length - if (model.contextLength != null) { - Text( - text = "Context Length: ${model.contextLength}", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } + // TODO-han.yin: make use of GGUF metadata // Last used date model.lastUsed?.let { lastUsed -> diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ModelsManagementScreen.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ModelsManagementScreen.kt index 54ef7cad30..8ef5b175c7 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ModelsManagementScreen.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/ui/screens/ModelsManagementScreen.kt @@ -85,7 +85,7 @@ fun ModelsManagementScreen( if (isMultiSelectionMode) { viewModel.toggleModelSelectionById(model.id) } else { - viewModel.viewModelDetails(model.id) + viewModel.viewModelDetails(model) } }, modifier = Modifier.padding(bottom = 8.dp), @@ -95,7 +95,7 @@ fun ModelsManagementScreen( if (!isMultiSelectionMode) { { ModelCardActions.InfoButton( - onClick = { viewModel.viewModelDetails(model.id) } + onClick = { viewModel.viewModelDetails(model) } ) } } else null diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/util/GgufMetadata.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/util/GgufMetadata.kt index 62f6036780..4172b6aebc 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/util/GgufMetadata.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/util/GgufMetadata.kt @@ -1,11 +1,15 @@ package com.example.llama.revamp.util +import androidx.room.TypeConverter +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import java.io.IOException /** * Structured metadata of GGUF */ +@Serializable data class GgufMetadata( // Basic file info val version: GgufVersion, @@ -69,6 +73,7 @@ data class GgufMetadata( val formattedContextLength: String? get() = dimensions?.contextLength?.let { "$it tokens" } + @Serializable enum class GgufVersion(val code: Int, val label: String) { /** First public draft; little‑endian only, no alignment key. */ LEGACY_V1(1, "Legacy v1"), @@ -88,6 +93,7 @@ data class GgufMetadata( override fun toString(): String = "$label (code=$code)" } + @Serializable data class BasicInfo( val uuid: String? = null, val name: String? = null, @@ -95,6 +101,7 @@ data class GgufMetadata( val sizeLabel: String? = null, // Size label like "7B" ) + @Serializable data class AuthorInfo( val organization: String? = null, val author: String? = null, @@ -105,6 +112,7 @@ data class GgufMetadata( val licenseLink: String? = null, ) + @Serializable data class AdditionalInfo( val type: String? = null, val description: String? = null, @@ -112,6 +120,7 @@ data class GgufMetadata( val languages: List? = null, ) + @Serializable data class ArchitectureInfo( val architecture: String? = null, val fileType: Int? = null, @@ -120,6 +129,7 @@ data class GgufMetadata( val quantizationVersion: Int? = null, ) + @Serializable data class BaseModelInfo( val name: String? = null, val author: String? = null, @@ -131,6 +141,7 @@ data class GgufMetadata( val repoUrl: String? = null, ) + @Serializable data class TokenizerInfo( val model: String? = null, val bosTokenId: Int? = null, @@ -142,6 +153,7 @@ data class GgufMetadata( val chatTemplate: String? = null, ) + @Serializable data class DimensionsInfo( val contextLength: Int? = null, val embeddingSize: Int? = null, @@ -149,6 +161,7 @@ data class GgufMetadata( val feedForwardSize: Int? = null, ) + @Serializable data class AttentionInfo( val headCount: Int? = null, val headCountKv: Int? = null, @@ -158,6 +171,7 @@ data class GgufMetadata( val layerNormRmsEpsilon: Float? = null, ) + @Serializable data class RopeInfo( val frequencyBase: Float? = null, val dimensionCount: Int? = null, @@ -168,12 +182,25 @@ data class GgufMetadata( val finetuned: Boolean? = null, ) + @Serializable data class ExpertsInfo( val count: Int? = null, val usedCount: Int? = null, ) } +class GgufMetadataConverters { + private val json = Json { encodeDefaults = false; ignoreUnknownKeys = true } + + @TypeConverter + fun toJson(value: GgufMetadata?): String? = + value?.let { json.encodeToString(GgufMetadata.serializer(), it) } + + @TypeConverter + fun fromJson(value: String?): GgufMetadata? = + value?.let { json.decodeFromString(GgufMetadata.serializer(), it) } +} + /** * Numerical codes used by `general.file_type` (see llama.cpp repo's `constants.py`). * The `label` matches what llama‑cli prints. diff --git a/examples/llama.android/app/src/main/java/com/example/llama/revamp/viewmodel/ModelsManagementViewModel.kt b/examples/llama.android/app/src/main/java/com/example/llama/revamp/viewmodel/ModelsManagementViewModel.kt index e3ee9a48f2..9c552a1d9f 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/revamp/viewmodel/ModelsManagementViewModel.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/revamp/viewmodel/ModelsManagementViewModel.kt @@ -2,6 +2,7 @@ package com.example.llama.revamp.viewmodel import android.content.Context import android.net.Uri +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.llama.revamp.data.model.ModelInfo @@ -126,8 +127,9 @@ class ModelsManagementViewModel @Inject constructor( _managementState.value = ModelManagementState.Idle } - fun viewModelDetails(modelId: String) { - // TODO-han.yin: Stub for now. Would navigate to model details screen or show dialog + fun viewModelDetails(model: ModelInfo) { + // TODO-han.yin: print logs for now. Would navigate to model details screen or show dialog + Log.i(TAG, "${model.name} metadata: \n" + model.metadata.toString()) } /**