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.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.TypeConverters
|
||||||
import com.example.llama.revamp.data.model.ModelInfo
|
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")
|
@Entity(tableName = "models")
|
||||||
data class ModelEntity(
|
data class ModelEntity(
|
||||||
|
|
@ -13,6 +15,8 @@ data class ModelEntity(
|
||||||
val name: String,
|
val name: String,
|
||||||
val path: String,
|
val path: String,
|
||||||
val sizeInBytes: Long,
|
val sizeInBytes: Long,
|
||||||
|
@field:TypeConverters(GgufMetadataConverters::class)
|
||||||
|
val metadata: GgufMetadata,
|
||||||
val dateAdded: Long,
|
val dateAdded: Long,
|
||||||
val lastUsed: Long?
|
val lastUsed: Long?
|
||||||
) {
|
) {
|
||||||
|
|
@ -21,6 +25,7 @@ data class ModelEntity(
|
||||||
name = name,
|
name = name,
|
||||||
path = path,
|
path = path,
|
||||||
sizeInBytes = sizeInBytes,
|
sizeInBytes = sizeInBytes,
|
||||||
|
metadata = metadata,
|
||||||
lastUsed = lastUsed,
|
lastUsed = lastUsed,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.example.llama.revamp.data.model
|
package com.example.llama.revamp.data.model
|
||||||
|
|
||||||
|
import com.example.llama.revamp.util.GgufMetadata
|
||||||
import com.example.llama.revamp.util.formatSize
|
import com.example.llama.revamp.util.formatSize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -10,6 +11,7 @@ data class ModelInfo(
|
||||||
val name: String,
|
val name: String,
|
||||||
val path: String,
|
val path: String,
|
||||||
val sizeInBytes: Long,
|
val sizeInBytes: Long,
|
||||||
|
val metadata: GgufMetadata,
|
||||||
val lastUsed: Long? = null
|
val lastUsed: Long? = null
|
||||||
) {
|
) {
|
||||||
val formattedSize: String
|
val formattedSize: String
|
||||||
|
|
|
||||||
|
|
@ -192,8 +192,8 @@ class ModelRepositoryImpl @Inject constructor(
|
||||||
Log.i(TAG, "Extracting GGUF Metadata from $filePath")
|
Log.i(TAG, "Extracting GGUF Metadata from $filePath")
|
||||||
GgufMetadataReader().readStructuredMetadata(filePath)
|
GgufMetadataReader().readStructuredMetadata(filePath)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to extract GGUF metadata: ${e.message}", e)
|
Log.e(TAG, "Cannot extract GGUF metadata: ${e.message}", e)
|
||||||
null
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create model entity and save via DAO
|
// Create model entity and save via DAO
|
||||||
|
|
@ -202,15 +202,11 @@ class ModelRepositoryImpl @Inject constructor(
|
||||||
name = fileName.substringBeforeLast('.'),
|
name = fileName.substringBeforeLast('.'),
|
||||||
path = modelFile.absolutePath,
|
path = modelFile.absolutePath,
|
||||||
sizeInBytes = modelFile.length(),
|
sizeInBytes = modelFile.length(),
|
||||||
// TODO-han.yin: add metadata here
|
metadata = metadata,
|
||||||
dateAdded = System.currentTimeMillis(),
|
dateAdded = System.currentTimeMillis(),
|
||||||
lastUsed = null
|
lastUsed = null
|
||||||
).let {
|
).let {
|
||||||
modelDao.insertModel(it)
|
modelDao.insertModel(it)
|
||||||
|
|
||||||
importJob = null
|
|
||||||
currentModelFile = null
|
|
||||||
|
|
||||||
it.toModelInfo()
|
it.toModelInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,41 +174,7 @@ private fun ModelInfoContent(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
// Model details row (parameters, quantization, size)
|
// TODO-han.yin: make use of GGUF metadata
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last used date
|
// Last used date
|
||||||
model.lastUsed?.let { lastUsed ->
|
model.lastUsed?.let { lastUsed ->
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ fun ModelsManagementScreen(
|
||||||
if (isMultiSelectionMode) {
|
if (isMultiSelectionMode) {
|
||||||
viewModel.toggleModelSelectionById(model.id)
|
viewModel.toggleModelSelectionById(model.id)
|
||||||
} else {
|
} else {
|
||||||
viewModel.viewModelDetails(model.id)
|
viewModel.viewModelDetails(model)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(bottom = 8.dp),
|
modifier = Modifier.padding(bottom = 8.dp),
|
||||||
|
|
@ -95,7 +95,7 @@ fun ModelsManagementScreen(
|
||||||
if (!isMultiSelectionMode) {
|
if (!isMultiSelectionMode) {
|
||||||
{
|
{
|
||||||
ModelCardActions.InfoButton(
|
ModelCardActions.InfoButton(
|
||||||
onClick = { viewModel.viewModelDetails(model.id) }
|
onClick = { viewModel.viewModelDetails(model) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else null
|
} else null
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
package com.example.llama.revamp.util
|
package com.example.llama.revamp.util
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Structured metadata of GGUF
|
* Structured metadata of GGUF
|
||||||
*/
|
*/
|
||||||
|
@Serializable
|
||||||
data class GgufMetadata(
|
data class GgufMetadata(
|
||||||
// Basic file info
|
// Basic file info
|
||||||
val version: GgufVersion,
|
val version: GgufVersion,
|
||||||
|
|
@ -69,6 +73,7 @@ data class GgufMetadata(
|
||||||
val formattedContextLength: String?
|
val formattedContextLength: String?
|
||||||
get() = dimensions?.contextLength?.let { "$it tokens" }
|
get() = dimensions?.contextLength?.let { "$it tokens" }
|
||||||
|
|
||||||
|
@Serializable
|
||||||
enum class GgufVersion(val code: Int, val label: String) {
|
enum class GgufVersion(val code: Int, val label: String) {
|
||||||
/** First public draft; little‑endian only, no alignment key. */
|
/** First public draft; little‑endian only, no alignment key. */
|
||||||
LEGACY_V1(1, "Legacy v1"),
|
LEGACY_V1(1, "Legacy v1"),
|
||||||
|
|
@ -88,6 +93,7 @@ data class GgufMetadata(
|
||||||
override fun toString(): String = "$label (code=$code)"
|
override fun toString(): String = "$label (code=$code)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class BasicInfo(
|
data class BasicInfo(
|
||||||
val uuid: String? = null,
|
val uuid: String? = null,
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
|
|
@ -95,6 +101,7 @@ data class GgufMetadata(
|
||||||
val sizeLabel: String? = null, // Size label like "7B"
|
val sizeLabel: String? = null, // Size label like "7B"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class AuthorInfo(
|
data class AuthorInfo(
|
||||||
val organization: String? = null,
|
val organization: String? = null,
|
||||||
val author: String? = null,
|
val author: String? = null,
|
||||||
|
|
@ -105,6 +112,7 @@ data class GgufMetadata(
|
||||||
val licenseLink: String? = null,
|
val licenseLink: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class AdditionalInfo(
|
data class AdditionalInfo(
|
||||||
val type: String? = null,
|
val type: String? = null,
|
||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
|
|
@ -112,6 +120,7 @@ data class GgufMetadata(
|
||||||
val languages: List<String>? = null,
|
val languages: List<String>? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ArchitectureInfo(
|
data class ArchitectureInfo(
|
||||||
val architecture: String? = null,
|
val architecture: String? = null,
|
||||||
val fileType: Int? = null,
|
val fileType: Int? = null,
|
||||||
|
|
@ -120,6 +129,7 @@ data class GgufMetadata(
|
||||||
val quantizationVersion: Int? = null,
|
val quantizationVersion: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class BaseModelInfo(
|
data class BaseModelInfo(
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
val author: String? = null,
|
val author: String? = null,
|
||||||
|
|
@ -131,6 +141,7 @@ data class GgufMetadata(
|
||||||
val repoUrl: String? = null,
|
val repoUrl: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class TokenizerInfo(
|
data class TokenizerInfo(
|
||||||
val model: String? = null,
|
val model: String? = null,
|
||||||
val bosTokenId: Int? = null,
|
val bosTokenId: Int? = null,
|
||||||
|
|
@ -142,6 +153,7 @@ data class GgufMetadata(
|
||||||
val chatTemplate: String? = null,
|
val chatTemplate: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class DimensionsInfo(
|
data class DimensionsInfo(
|
||||||
val contextLength: Int? = null,
|
val contextLength: Int? = null,
|
||||||
val embeddingSize: Int? = null,
|
val embeddingSize: Int? = null,
|
||||||
|
|
@ -149,6 +161,7 @@ data class GgufMetadata(
|
||||||
val feedForwardSize: Int? = null,
|
val feedForwardSize: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class AttentionInfo(
|
data class AttentionInfo(
|
||||||
val headCount: Int? = null,
|
val headCount: Int? = null,
|
||||||
val headCountKv: Int? = null,
|
val headCountKv: Int? = null,
|
||||||
|
|
@ -158,6 +171,7 @@ data class GgufMetadata(
|
||||||
val layerNormRmsEpsilon: Float? = null,
|
val layerNormRmsEpsilon: Float? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class RopeInfo(
|
data class RopeInfo(
|
||||||
val frequencyBase: Float? = null,
|
val frequencyBase: Float? = null,
|
||||||
val dimensionCount: Int? = null,
|
val dimensionCount: Int? = null,
|
||||||
|
|
@ -168,12 +182,25 @@ data class GgufMetadata(
|
||||||
val finetuned: Boolean? = null,
|
val finetuned: Boolean? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ExpertsInfo(
|
data class ExpertsInfo(
|
||||||
val count: Int? = null,
|
val count: Int? = null,
|
||||||
val usedCount: 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`).
|
* Numerical codes used by `general.file_type` (see llama.cpp repo's `constants.py`).
|
||||||
* The `label` matches what llama‑cli prints.
|
* The `label` matches what llama‑cli prints.
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.example.llama.revamp.viewmodel
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.example.llama.revamp.data.model.ModelInfo
|
import com.example.llama.revamp.data.model.ModelInfo
|
||||||
|
|
@ -126,8 +127,9 @@ class ModelsManagementViewModel @Inject constructor(
|
||||||
_managementState.value = ModelManagementState.Idle
|
_managementState.value = ModelManagementState.Idle
|
||||||
}
|
}
|
||||||
|
|
||||||
fun viewModelDetails(modelId: String) {
|
fun viewModelDetails(model: ModelInfo) {
|
||||||
// TODO-han.yin: Stub for now. Would navigate to model details screen or show dialog
|
// 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