[WIP] lib: move GgufMetadata into the lib submodule
This commit is contained in:
parent
4b3f6ef8d7
commit
6a5bc94ff1
|
|
@ -1,7 +1,7 @@
|
|||
package com.example.llama.data.local.converter
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import com.example.llama.util.GgufMetadata
|
||||
import com.example.llama.data.model.GgufMetadata
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class GgufMetadataConverters {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import androidx.room.PrimaryKey
|
|||
import androidx.room.TypeConverters
|
||||
import com.example.llama.data.local.converter.GgufMetadataConverters
|
||||
import com.example.llama.data.model.ModelInfo
|
||||
import com.example.llama.util.GgufMetadata
|
||||
import com.example.llama.data.model.GgufMetadata
|
||||
|
||||
|
||||
@Entity(tableName = "models")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,309 @@
|
|||
package com.example.llama.data.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import android.llama.cpp.gguf.GgufMetadata as Domain
|
||||
|
||||
|
||||
/**
|
||||
* A local serializable domain replicate of [android.llama.cpp.gguf.GgufMetadata]
|
||||
*/
|
||||
@Serializable
|
||||
data class GgufMetadata(
|
||||
// Basic file info
|
||||
val version: GgufVersion,
|
||||
val tensorCount: Long,
|
||||
val kvCount: Long,
|
||||
|
||||
// General info
|
||||
val basic: BasicInfo,
|
||||
val author: AuthorInfo? = null,
|
||||
val additional: AdditionalInfo? = null,
|
||||
val architecture: ArchitectureInfo? = null,
|
||||
val baseModels: List<BaseModelInfo>? = null,
|
||||
val tokenizer: TokenizerInfo? = null,
|
||||
|
||||
// Derivative info
|
||||
val dimensions: DimensionsInfo? = null,
|
||||
val attention: AttentionInfo? = null,
|
||||
val rope: RopeInfo? = null,
|
||||
val experts: ExpertsInfo? = null
|
||||
) {
|
||||
/** Human-readable full model name + size */
|
||||
val fullModelName: String?
|
||||
get() = when {
|
||||
basic.nameLabel != null -> basic.nameLabel
|
||||
basic.name != null && basic.sizeLabel != null -> "${basic.name}-${basic.sizeLabel}"
|
||||
basic.name != null -> basic.name
|
||||
else -> null
|
||||
}
|
||||
|
||||
/** Human‑readable model name (spaces). */
|
||||
val primaryName: String?
|
||||
get() = basic.nameLabel
|
||||
?: baseModels?.firstNotNullOfOrNull { it.name }
|
||||
?: basic.name
|
||||
|
||||
/** CLI‑friendly slug (hyphens). */
|
||||
val primaryBasename: String?
|
||||
get() = basic.name
|
||||
?: baseModels?.firstNotNullOfOrNull { it.name?.replace(' ', '-') }
|
||||
|
||||
/** URL pointing to model homepage/repo. */
|
||||
val primaryUrl: String?
|
||||
get() = author?.url
|
||||
?: baseModels?.firstNotNullOfOrNull { it.url }
|
||||
|
||||
val primaryRepoUrl: String?
|
||||
get() = author?.repoUrl
|
||||
?: baseModels?.firstNotNullOfOrNull { it.repoUrl }
|
||||
|
||||
/** Organisation string. */
|
||||
val primaryOrganization: String?
|
||||
get() = author?.organization
|
||||
?: baseModels?.firstNotNullOfOrNull { it.organization }
|
||||
|
||||
/** Author string. */
|
||||
val primaryAuthor: String?
|
||||
get() = author?.author
|
||||
?: baseModels?.firstNotNullOfOrNull { it.author }
|
||||
|
||||
@Serializable
|
||||
enum class GgufVersion(val code: Int, val label: String) {
|
||||
/** First public draft; little‑endian only, no alignment key. */
|
||||
LEGACY_V1(1, "Legacy v1"),
|
||||
|
||||
/** Added split‑file support and some extra metadata keys. */
|
||||
EXTENDED_V2(2, "Extended v2"),
|
||||
|
||||
/** Current spec: endian‑aware, mandatory alignment, fully validated. */
|
||||
VALIDATED_V3(3, "Validated v3");
|
||||
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.GgufVersion): GgufVersion = when (domain) {
|
||||
Domain.GgufVersion.LEGACY_V1 -> LEGACY_V1
|
||||
Domain.GgufVersion.EXTENDED_V2 -> EXTENDED_V2
|
||||
Domain.GgufVersion.VALIDATED_V3 -> VALIDATED_V3
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = "$label (code=$code)"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BasicInfo(
|
||||
val uuid: String? = null,
|
||||
val name: String? = null,
|
||||
val nameLabel: String? = null,
|
||||
val sizeLabel: String? = null, // Size label like "7B"
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.BasicInfo) = BasicInfo(
|
||||
uuid = domain.uuid,
|
||||
name = domain.name,
|
||||
nameLabel = domain.nameLabel,
|
||||
sizeLabel = domain.sizeLabel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AuthorInfo(
|
||||
val organization: String? = null,
|
||||
val author: String? = null,
|
||||
val doi: String? = null,
|
||||
val url: String? = null,
|
||||
val repoUrl: String? = null,
|
||||
val license: String? = null,
|
||||
val licenseLink: String? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.AuthorInfo) = AuthorInfo(
|
||||
organization = domain.organization,
|
||||
author = domain.author,
|
||||
doi = domain.doi,
|
||||
url = domain.url,
|
||||
repoUrl = domain.repoUrl,
|
||||
license = domain.license,
|
||||
licenseLink = domain.licenseLink
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AdditionalInfo(
|
||||
val type: String? = null,
|
||||
val description: String? = null,
|
||||
val tags: List<String>? = null,
|
||||
val languages: List<String>? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.AdditionalInfo) = AdditionalInfo(
|
||||
type = domain.type,
|
||||
description = domain.description,
|
||||
tags = domain.tags,
|
||||
languages = domain.languages
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ArchitectureInfo(
|
||||
val architecture: String? = null,
|
||||
val fileType: Int? = null,
|
||||
val vocabSize: Int? = null,
|
||||
val finetune: String? = null,
|
||||
val quantizationVersion: Int? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.ArchitectureInfo) = ArchitectureInfo(
|
||||
architecture = domain.architecture,
|
||||
fileType = domain.fileType,
|
||||
vocabSize = domain.vocabSize,
|
||||
finetune = domain.finetune,
|
||||
quantizationVersion = domain.quantizationVersion
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BaseModelInfo(
|
||||
val name: String? = null,
|
||||
val author: String? = null,
|
||||
val version: String? = null,
|
||||
val organization: String? = null,
|
||||
val url: String? = null,
|
||||
val doi: String? = null,
|
||||
val uuid: String? = null,
|
||||
val repoUrl: String? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.BaseModelInfo) = BaseModelInfo(
|
||||
name = domain.name,
|
||||
author = domain.author,
|
||||
version = domain.version,
|
||||
organization = domain.organization,
|
||||
url = domain.url,
|
||||
doi = domain.doi,
|
||||
uuid = domain.uuid,
|
||||
repoUrl = domain.repoUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class TokenizerInfo(
|
||||
val model: String? = null,
|
||||
val bosTokenId: Int? = null,
|
||||
val eosTokenId: Int? = null,
|
||||
val unknownTokenId: Int? = null,
|
||||
val paddingTokenId: Int? = null,
|
||||
val addBosToken: Boolean? = null,
|
||||
val addEosToken: Boolean? = null,
|
||||
val chatTemplate: String? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.TokenizerInfo) = TokenizerInfo(
|
||||
model = domain.model,
|
||||
bosTokenId = domain.bosTokenId,
|
||||
eosTokenId = domain.eosTokenId,
|
||||
unknownTokenId = domain.unknownTokenId,
|
||||
paddingTokenId = domain.paddingTokenId,
|
||||
addBosToken = domain.addBosToken,
|
||||
addEosToken = domain.addEosToken,
|
||||
chatTemplate = domain.chatTemplate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class DimensionsInfo(
|
||||
val contextLength: Int? = null,
|
||||
val embeddingSize: Int? = null,
|
||||
val blockCount: Int? = null,
|
||||
val feedForwardSize: Int? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.DimensionsInfo) = DimensionsInfo(
|
||||
contextLength = domain.contextLength,
|
||||
embeddingSize = domain.embeddingSize,
|
||||
blockCount = domain.blockCount,
|
||||
feedForwardSize = domain.feedForwardSize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AttentionInfo(
|
||||
val headCount: Int? = null,
|
||||
val headCountKv: Int? = null,
|
||||
val keyLength: Int? = null,
|
||||
val valueLength: Int? = null,
|
||||
val layerNormEpsilon: Float? = null,
|
||||
val layerNormRmsEpsilon: Float? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.AttentionInfo) = AttentionInfo(
|
||||
headCount = domain.headCount,
|
||||
headCountKv = domain.headCountKv,
|
||||
keyLength = domain.keyLength,
|
||||
valueLength = domain.valueLength,
|
||||
layerNormEpsilon = domain.layerNormEpsilon,
|
||||
layerNormRmsEpsilon = domain.layerNormRmsEpsilon
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class RopeInfo(
|
||||
val frequencyBase: Float? = null,
|
||||
val dimensionCount: Int? = null,
|
||||
val scalingType: String? = null,
|
||||
val scalingFactor: Float? = null,
|
||||
val attnFactor: Float? = null,
|
||||
val originalContextLength: Int? = null,
|
||||
val finetuned: Boolean? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.RopeInfo) = RopeInfo(
|
||||
frequencyBase = domain.frequencyBase,
|
||||
dimensionCount = domain.dimensionCount,
|
||||
scalingType = domain.scalingType,
|
||||
scalingFactor = domain.scalingFactor,
|
||||
attnFactor = domain.attnFactor,
|
||||
originalContextLength = domain.originalContextLength,
|
||||
finetuned = domain.finetuned
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ExpertsInfo(
|
||||
val count: Int? = null,
|
||||
val usedCount: Int? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain.ExpertsInfo) = ExpertsInfo(
|
||||
count = domain.count,
|
||||
usedCount = domain.usedCount
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromDomain(domain: Domain) = GgufMetadata(
|
||||
version = GgufVersion.fromDomain(domain.version),
|
||||
tensorCount = domain.tensorCount,
|
||||
kvCount = domain.kvCount,
|
||||
basic = BasicInfo.fromDomain(domain.basic),
|
||||
author = domain.author?.let { AuthorInfo.fromDomain(it) },
|
||||
additional = domain.additional?.let { AdditionalInfo.fromDomain(it) },
|
||||
architecture = domain.architecture?.let { ArchitectureInfo.fromDomain(it) },
|
||||
baseModels = domain.baseModels?.map { BaseModelInfo.fromDomain(it) },
|
||||
tokenizer = domain.tokenizer?.let { TokenizerInfo.fromDomain(it) },
|
||||
dimensions = domain.dimensions?.let { DimensionsInfo.fromDomain(it) },
|
||||
attention = domain.attention?.let { AttentionInfo.fromDomain(it) },
|
||||
rope = domain.rope?.let { RopeInfo.fromDomain(it) },
|
||||
experts = domain.experts?.let { ExpertsInfo.fromDomain(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package com.example.llama.data.model
|
||||
|
||||
import com.example.llama.util.FileType
|
||||
import com.example.llama.util.GgufMetadata
|
||||
import android.llama.cpp.gguf.FileType
|
||||
import com.example.llama.util.formatContextLength
|
||||
import com.example.llama.util.formatFileByteSize
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
package com.example.llama.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.llama.cpp.gguf.GgufMetadataReader
|
||||
import android.net.Uri
|
||||
import android.os.StatFs
|
||||
import android.util.Log
|
||||
import com.example.llama.data.local.dao.ModelDao
|
||||
import com.example.llama.data.local.entity.ModelEntity
|
||||
import com.example.llama.data.model.GgufMetadata
|
||||
import com.example.llama.data.model.ModelInfo
|
||||
import com.example.llama.data.remote.HuggingFaceModel
|
||||
import com.example.llama.data.remote.HuggingFaceRemoteDataSource
|
||||
import com.example.llama.data.repository.ModelRepository.ImportProgressTracker
|
||||
import com.example.llama.monitoring.StorageMetrics
|
||||
import com.example.llama.util.GgufMetadata
|
||||
import com.example.llama.util.GgufMetadataReader
|
||||
import com.example.llama.util.copyWithBuffer
|
||||
import com.example.llama.util.copyWithChannels
|
||||
import com.example.llama.util.formatFileByteSize
|
||||
|
|
@ -224,7 +224,7 @@ class ModelRepositoryImpl @Inject constructor(
|
|||
val metadata = try {
|
||||
val filePath = modelFile.absolutePath
|
||||
Log.i(TAG, "Extracting GGUF Metadata from $filePath")
|
||||
GgufMetadataReader().readStructuredMetadata(filePath)
|
||||
GgufMetadata.fromDomain(GgufMetadataReader().readStructuredMetadata(filePath))
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Cannot extract GGUF metadata: ${e.message}", e)
|
||||
throw e
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.example.llama.ui.screens
|
||||
|
||||
import android.llama.cpp.gguf.FileType
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
|
|
@ -25,7 +26,6 @@ import com.example.llama.data.model.ModelInfo
|
|||
import com.example.llama.ui.components.ModelCardContentArchitectureRow
|
||||
import com.example.llama.ui.components.ModelCardContentContextRow
|
||||
import com.example.llama.ui.components.ModelCardContentTitleRow
|
||||
import com.example.llama.util.FileType
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package com.example.llama.util
|
||||
package android.llama.cpp.gguf
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
/**
|
||||
* Structured metadata of GGUF
|
||||
*/
|
||||
@Serializable
|
||||
data class GgufMetadata(
|
||||
// Basic file info
|
||||
val version: GgufVersion,
|
||||
|
|
@ -28,46 +26,6 @@ data class GgufMetadata(
|
|||
val rope: RopeInfo? = null,
|
||||
val experts: ExpertsInfo? = null
|
||||
) {
|
||||
/** Human-readable full model name + size */
|
||||
val fullModelName: String?
|
||||
get() = when {
|
||||
basic.nameLabel != null -> basic.nameLabel
|
||||
basic.name != null && basic.sizeLabel != null -> "${basic.name}-${basic.sizeLabel}"
|
||||
basic.name != null -> basic.name
|
||||
else -> null
|
||||
}
|
||||
|
||||
/** Human‑readable model name (spaces). */
|
||||
val primaryName: String?
|
||||
get() = basic.nameLabel
|
||||
?: baseModels?.firstNotNullOfOrNull { it.name }
|
||||
?: basic.name
|
||||
|
||||
/** CLI‑friendly slug (hyphens). */
|
||||
val primaryBasename: String?
|
||||
get() = basic.name
|
||||
?: baseModels?.firstNotNullOfOrNull { it.name?.replace(' ', '-') }
|
||||
|
||||
/** URL pointing to model homepage/repo. */
|
||||
val primaryUrl: String?
|
||||
get() = author?.url
|
||||
?: baseModels?.firstNotNullOfOrNull { it.url }
|
||||
|
||||
val primaryRepoUrl: String?
|
||||
get() = author?.repoUrl
|
||||
?: baseModels?.firstNotNullOfOrNull { it.repoUrl }
|
||||
|
||||
/** Organisation string. */
|
||||
val primaryOrganization: String?
|
||||
get() = author?.organization
|
||||
?: baseModels?.firstNotNullOfOrNull { it.organization }
|
||||
|
||||
/** Author string. */
|
||||
val primaryAuthor: String?
|
||||
get() = author?.author
|
||||
?: baseModels?.firstNotNullOfOrNull { it.author }
|
||||
|
||||
@Serializable
|
||||
enum class GgufVersion(val code: Int, val label: String) {
|
||||
/** First public draft; little‑endian only, no alignment key. */
|
||||
LEGACY_V1(1, "Legacy v1"),
|
||||
|
|
@ -87,7 +45,6 @@ data class GgufMetadata(
|
|||
override fun toString(): String = "$label (code=$code)"
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BasicInfo(
|
||||
val uuid: String? = null,
|
||||
val name: String? = null,
|
||||
|
|
@ -95,7 +52,6 @@ data class GgufMetadata(
|
|||
val sizeLabel: String? = null, // Size label like "7B"
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AuthorInfo(
|
||||
val organization: String? = null,
|
||||
val author: String? = null,
|
||||
|
|
@ -106,7 +62,6 @@ data class GgufMetadata(
|
|||
val licenseLink: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AdditionalInfo(
|
||||
val type: String? = null,
|
||||
val description: String? = null,
|
||||
|
|
@ -114,7 +69,6 @@ data class GgufMetadata(
|
|||
val languages: List<String>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ArchitectureInfo(
|
||||
val architecture: String? = null,
|
||||
val fileType: Int? = null,
|
||||
|
|
@ -123,7 +77,6 @@ data class GgufMetadata(
|
|||
val quantizationVersion: Int? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BaseModelInfo(
|
||||
val name: String? = null,
|
||||
val author: String? = null,
|
||||
|
|
@ -135,7 +88,6 @@ data class GgufMetadata(
|
|||
val repoUrl: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TokenizerInfo(
|
||||
val model: String? = null,
|
||||
val bosTokenId: Int? = null,
|
||||
|
|
@ -147,7 +99,6 @@ data class GgufMetadata(
|
|||
val chatTemplate: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DimensionsInfo(
|
||||
val contextLength: Int? = null,
|
||||
val embeddingSize: Int? = null,
|
||||
|
|
@ -155,7 +106,6 @@ data class GgufMetadata(
|
|||
val feedForwardSize: Int? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AttentionInfo(
|
||||
val headCount: Int? = null,
|
||||
val headCountKv: Int? = null,
|
||||
|
|
@ -165,7 +115,6 @@ data class GgufMetadata(
|
|||
val layerNormRmsEpsilon: Float? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RopeInfo(
|
||||
val frequencyBase: Float? = null,
|
||||
val dimensionCount: Int? = null,
|
||||
|
|
@ -176,7 +125,6 @@ data class GgufMetadata(
|
|||
val finetuned: Boolean? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ExpertsInfo(
|
||||
val count: Int? = null,
|
||||
val usedCount: Int? = null,
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.example.llama.util
|
||||
package android.llama.cpp.gguf
|
||||
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
private val DEFAULT_SKIP_KEYS = setOf(
|
||||
"tokenizer.chat_template",
|
||||
Loading…
Reference in New Issue