GGUF: make GgufMetadata serializable in order to be compatible with Room
This commit is contained in:
parent
8ae0c3d2fa
commit
7ed79319e5
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String>? = 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.
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue