diff --git a/examples/llama.android/app/src/main/java/com/example/llama/data/local/converter/GgufMetadataConverters.kt b/examples/llama.android/app/src/main/java/com/example/llama/data/local/converter/GgufMetadataConverters.kt index 8302f591dd..7c3e2a85ef 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/data/local/converter/GgufMetadataConverters.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/data/local/converter/GgufMetadataConverters.kt @@ -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 { diff --git a/examples/llama.android/app/src/main/java/com/example/llama/data/local/entity/ModelEntity.kt b/examples/llama.android/app/src/main/java/com/example/llama/data/local/entity/ModelEntity.kt index 8bc65b30c7..ebd590053e 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/data/local/entity/ModelEntity.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/data/local/entity/ModelEntity.kt @@ -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") diff --git a/examples/llama.android/app/src/main/java/com/example/llama/data/model/GgufMetadata.kt b/examples/llama.android/app/src/main/java/com/example/llama/data/model/GgufMetadata.kt new file mode 100644 index 0000000000..249a63b1a0 --- /dev/null +++ b/examples/llama.android/app/src/main/java/com/example/llama/data/model/GgufMetadata.kt @@ -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? = 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? = null, + val languages: List? = 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) } + ) + } +} diff --git a/examples/llama.android/app/src/main/java/com/example/llama/data/model/ModelInfo.kt b/examples/llama.android/app/src/main/java/com/example/llama/data/model/ModelInfo.kt index 185ead092b..757f6568b8 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/data/model/ModelInfo.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/data/model/ModelInfo.kt @@ -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 diff --git a/examples/llama.android/app/src/main/java/com/example/llama/data/repository/ModelRepository.kt b/examples/llama.android/app/src/main/java/com/example/llama/data/repository/ModelRepository.kt index 91f89cf025..bcc3721e36 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/data/repository/ModelRepository.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/data/repository/ModelRepository.kt @@ -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 diff --git a/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelDetailsScreen.kt b/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelDetailsScreen.kt index 1d0a919c13..e3cdf4cbe1 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelDetailsScreen.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/ui/screens/ModelDetailsScreen.kt @@ -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 diff --git a/examples/llama.android/app/src/main/java/com/example/llama/util/GgufMetadata.kt b/examples/llama.android/llama/src/main/java/android/llama/cpp/gguf/GgufMetadata.kt similarity index 78% rename from examples/llama.android/app/src/main/java/com/example/llama/util/GgufMetadata.kt rename to examples/llama.android/llama/src/main/java/android/llama/cpp/gguf/GgufMetadata.kt index db26924a02..8a51e35e79 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/util/GgufMetadata.kt +++ b/examples/llama.android/llama/src/main/java/android/llama/cpp/gguf/GgufMetadata.kt @@ -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? = 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, diff --git a/examples/llama.android/app/src/main/java/com/example/llama/util/GgufMetadataReader.kt b/examples/llama.android/llama/src/main/java/android/llama/cpp/gguf/GgufMetadataReader.kt similarity index 98% rename from examples/llama.android/app/src/main/java/com/example/llama/util/GgufMetadataReader.kt rename to examples/llama.android/llama/src/main/java/android/llama/cpp/gguf/GgufMetadataReader.kt index 242e4e70d3..6c1652e788 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/util/GgufMetadataReader.kt +++ b/examples/llama.android/llama/src/main/java/android/llama/cpp/gguf/GgufMetadataReader.kt @@ -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", @@ -296,19 +296,19 @@ class GgufMetadataReader( ).takeUnless { count == null && usedCount == null } return GgufMetadata( - version = version, + version = version, tensorCount = tensorCnt, - kvCount = kvCnt, - basic = basic, - author = author, - additional = additional, + kvCount = kvCnt, + basic = basic, + author = author, + additional = additional, architecture = architectureInfo, - baseModels = baseModels, - tokenizer = tokenizer, - dimensions = dimensions, - attention = attention, - rope = rope, - experts = experts + baseModels = baseModels, + tokenizer = tokenizer, + dimensions = dimensions, + attention = attention, + rope = rope, + experts = experts ) }