UI: add quick action buttons to benchmark screen's result card
This commit is contained in:
parent
659f59e22a
commit
c848005d11
|
|
@ -259,9 +259,7 @@ fun AppContent(
|
||||||
|
|
||||||
// Benchmark screen
|
// Benchmark screen
|
||||||
currentRoute.startsWith(AppDestinations.BENCHMARK_ROUTE) -> {
|
currentRoute.startsWith(AppDestinations.BENCHMARK_ROUTE) -> {
|
||||||
val engineState by benchmarkViewModel.engineState.collectAsState()
|
|
||||||
val showModelCard by benchmarkViewModel.showModelCard.collectAsState()
|
val showModelCard by benchmarkViewModel.showModelCard.collectAsState()
|
||||||
val benchmarkResults by benchmarkViewModel.benchmarkResults.collectAsState()
|
|
||||||
|
|
||||||
ScaffoldConfig(
|
ScaffoldConfig(
|
||||||
topBarConfig = TopBarConfig.Performance(
|
topBarConfig = TopBarConfig.Performance(
|
||||||
|
|
@ -274,22 +272,9 @@ fun AppContent(
|
||||||
),
|
),
|
||||||
bottomBarConfig = BottomBarConfig.Benchmark(
|
bottomBarConfig = BottomBarConfig.Benchmark(
|
||||||
engineIdle = !engineState.isUninterruptible,
|
engineIdle = !engineState.isUninterruptible,
|
||||||
onShare = {
|
onShare = { benchmarkViewModel.shareResult(handleScaffoldEvent) },
|
||||||
benchmarkResults.lastOrNull()?.let {
|
onRerun = { benchmarkViewModel.rerunBenchmark(handleScaffoldEvent) },
|
||||||
handleScaffoldEvent(ScaffoldEvent.ShareText(it.text))
|
onClear = { benchmarkViewModel.clearResults(handleScaffoldEvent) },
|
||||||
}
|
|
||||||
},
|
|
||||||
onRerun = {
|
|
||||||
if (engineState.isUninterruptible) {
|
|
||||||
handleScaffoldEvent(ScaffoldEvent.ShowSnackbar(
|
|
||||||
message = "Benchmark already in progress!\n" +
|
|
||||||
"Please wait for the current run to complete."
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
benchmarkViewModel.runBenchmark()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClear = benchmarkViewModel::clearResults,
|
|
||||||
showModelCard = showModelCard,
|
showModelCard = showModelCard,
|
||||||
onToggleModelCard = benchmarkViewModel::toggleModelCard,
|
onToggleModelCard = benchmarkViewModel::toggleModelCard,
|
||||||
)
|
)
|
||||||
|
|
@ -478,6 +463,7 @@ fun AppContent(
|
||||||
|
|
||||||
BenchmarkScreen(
|
BenchmarkScreen(
|
||||||
loadingMetrics = metrics,
|
loadingMetrics = metrics,
|
||||||
|
onScaffoldEvent = handleScaffoldEvent,
|
||||||
onNavigateBack = { navigationActions.navigateUp() },
|
onNavigateBack = { navigationActions.navigateUp() },
|
||||||
viewModel = benchmarkViewModel
|
viewModel = benchmarkViewModel
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,22 +27,25 @@ fun BenchmarkBottomBar(
|
||||||
showModelCard: Boolean,
|
showModelCard: Boolean,
|
||||||
onToggleModelCard: (Boolean) -> Unit,
|
onToggleModelCard: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val controlTint =
|
||||||
|
if (engineIdle) MaterialTheme.colorScheme.onSurface
|
||||||
|
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
|
||||||
|
|
||||||
BottomAppBar(
|
BottomAppBar(
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = onRerun) {
|
IconButton(onClick = onRerun) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Replay,
|
imageVector = Icons.Default.Replay,
|
||||||
contentDescription = "Run the benchmark again",
|
contentDescription = "Run the benchmark again",
|
||||||
tint =
|
tint = controlTint
|
||||||
if (engineIdle) MaterialTheme.colorScheme.onSurface
|
|
||||||
else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(onClick = onClear) {
|
IconButton(onClick = onClear) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ClearAll,
|
imageVector = Icons.Default.ClearAll,
|
||||||
contentDescription = "Clear benchmark results"
|
contentDescription = "Clear benchmark results",
|
||||||
|
tint = controlTint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.example.llama.ui.screens
|
package com.example.llama.ui.screens
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.llama.cpp.InferenceEngine.State
|
import android.llama.cpp.InferenceEngine.State
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
|
|
@ -19,11 +21,17 @@ import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.material.icons.filled.Replay
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -36,10 +44,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.net.toUri
|
||||||
import com.example.llama.data.model.ModelInfo
|
import com.example.llama.data.model.ModelInfo
|
||||||
import com.example.llama.engine.ModelLoadingMetrics
|
import com.example.llama.engine.ModelLoadingMetrics
|
||||||
import com.example.llama.ui.components.ModelCardContentArchitectureRow
|
import com.example.llama.ui.components.ModelCardContentArchitectureRow
|
||||||
|
|
@ -47,6 +57,7 @@ import com.example.llama.ui.components.ModelCardContentContextRow
|
||||||
import com.example.llama.ui.components.ModelCardContentField
|
import com.example.llama.ui.components.ModelCardContentField
|
||||||
import com.example.llama.ui.components.ModelCardCoreExpandable
|
import com.example.llama.ui.components.ModelCardCoreExpandable
|
||||||
import com.example.llama.ui.components.ModelUnloadDialogHandler
|
import com.example.llama.ui.components.ModelUnloadDialogHandler
|
||||||
|
import com.example.llama.ui.scaffold.ScaffoldEvent
|
||||||
import com.example.llama.util.TableData
|
import com.example.llama.util.TableData
|
||||||
import com.example.llama.util.formatMilliSeconds
|
import com.example.llama.util.formatMilliSeconds
|
||||||
import com.example.llama.util.parseMarkdownTable
|
import com.example.llama.util.parseMarkdownTable
|
||||||
|
|
@ -57,9 +68,12 @@ import com.example.llama.viewmodel.BenchmarkViewModel
|
||||||
@Composable
|
@Composable
|
||||||
fun BenchmarkScreen(
|
fun BenchmarkScreen(
|
||||||
loadingMetrics: ModelLoadingMetrics,
|
loadingMetrics: ModelLoadingMetrics,
|
||||||
|
onScaffoldEvent: (ScaffoldEvent) -> Unit,
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
viewModel: BenchmarkViewModel
|
viewModel: BenchmarkViewModel
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
// View model states
|
// View model states
|
||||||
val engineState by viewModel.engineState.collectAsState()
|
val engineState by viewModel.engineState.collectAsState()
|
||||||
val unloadDialogState by viewModel.unloadModelState.collectAsState()
|
val unloadDialogState by viewModel.unloadModelState.collectAsState()
|
||||||
|
|
@ -86,6 +100,12 @@ fun BenchmarkScreen(
|
||||||
viewModel.onBackPressed(onNavigateBack)
|
viewModel.onBackPressed(onNavigateBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val onInfo = {
|
||||||
|
Toast.makeText(context, "Please refer to this post for more details on the benchmark methodology", Toast.LENGTH_SHORT).show()
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, "https://blog.steelph0enix.dev/posts/llama-cpp-guide/#llama-bench".toUri())
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
|
|
@ -96,7 +116,11 @@ fun BenchmarkScreen(
|
||||||
verticalArrangement = Arrangement.Bottom,
|
verticalArrangement = Arrangement.Bottom,
|
||||||
) {
|
) {
|
||||||
items(items = benchmarkResults) {
|
items(items = benchmarkResults) {
|
||||||
BenchmarkResultCard(it)
|
BenchmarkResultCard(
|
||||||
|
result = it,
|
||||||
|
onRerun = { viewModel.rerunBenchmark(onScaffoldEvent) },
|
||||||
|
onInfo = onInfo,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +210,11 @@ private fun ModelCardWithLoadingMetrics(
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BenchmarkResultCard(result: BenchmarkResult) {
|
fun BenchmarkResultCard(
|
||||||
|
result: BenchmarkResult,
|
||||||
|
onRerun: () -> Unit,
|
||||||
|
onInfo: () -> Unit,
|
||||||
|
) {
|
||||||
val rawTable = parseMarkdownTable(result.text.trimIndent())
|
val rawTable = parseMarkdownTable(result.text.trimIndent())
|
||||||
val model = rawTable.getColumn("model").firstOrNull() ?: "Unknown"
|
val model = rawTable.getColumn("model").firstOrNull() ?: "Unknown"
|
||||||
val parameters = rawTable.getColumn("params").firstOrNull() ?: "-"
|
val parameters = rawTable.getColumn("params").firstOrNull() ?: "-"
|
||||||
|
|
@ -236,6 +264,28 @@ fun BenchmarkResultCard(result: BenchmarkResult) {
|
||||||
BenchmarkResultTable(rawTable)
|
BenchmarkResultTable(rawTable)
|
||||||
|
|
||||||
ModelCardContentField("Time spent: ", formatMilliSeconds(result.duration))
|
ModelCardContentField("Time spent: ", formatMilliSeconds(result.duration))
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Row {
|
||||||
|
OutlinedButton(onClick = onRerun) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Replay,
|
||||||
|
contentDescription = "Run the benchmark again"
|
||||||
|
)
|
||||||
|
Text("Run again", modifier = Modifier.padding(start = 6.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
FilledTonalButton(onClick = onInfo) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Info,
|
||||||
|
contentDescription = "Information about what the result means"
|
||||||
|
)
|
||||||
|
Text("How to interpret", modifier = Modifier.padding(start = 6.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.llama.cpp.isUninterruptible
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.example.llama.data.model.ModelInfo
|
import com.example.llama.data.model.ModelInfo
|
||||||
import com.example.llama.engine.BenchmarkService
|
import com.example.llama.engine.BenchmarkService
|
||||||
|
import com.example.llama.ui.scaffold.ScaffoldEvent
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
@ -30,7 +31,7 @@ class BenchmarkViewModel @Inject constructor(
|
||||||
val benchmarkResults: StateFlow<List<BenchmarkResult>> = _benchmarkResults.asStateFlow()
|
val benchmarkResults: StateFlow<List<BenchmarkResult>> = _benchmarkResults.asStateFlow()
|
||||||
|
|
||||||
// UI state: Model card
|
// UI state: Model card
|
||||||
private val _showModelCard = MutableStateFlow(true)
|
private val _showModelCard = MutableStateFlow(false)
|
||||||
val showModelCard = _showModelCard.asStateFlow()
|
val showModelCard = _showModelCard.asStateFlow()
|
||||||
|
|
||||||
fun toggleModelCard(show: Boolean) {
|
fun toggleModelCard(show: Boolean) {
|
||||||
|
|
@ -68,10 +69,37 @@ class BenchmarkViewModel @Inject constructor(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun performCleanup() = clearResults()
|
override suspend fun performCleanup() { clearResults(null) }
|
||||||
|
|
||||||
fun clearResults() {
|
fun clearResults(onScaffoldEvent: ((ScaffoldEvent) -> Unit)?) =
|
||||||
|
if (engineState.value.isUninterruptible) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
_benchmarkResults.value = emptyList()
|
_benchmarkResults.value = emptyList()
|
||||||
|
onScaffoldEvent?.invoke(ScaffoldEvent.ShowSnackbar(
|
||||||
|
message = "All benchmark results cleared."
|
||||||
|
))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rerun the benchmark
|
||||||
|
*/
|
||||||
|
fun rerunBenchmark(onScaffoldEvent: (ScaffoldEvent) -> Unit) {
|
||||||
|
if (engineState.value.isUninterruptible) {
|
||||||
|
onScaffoldEvent(ScaffoldEvent.ShowSnackbar(
|
||||||
|
message = "Benchmark already in progress!\n" +
|
||||||
|
"Please wait for the current run to complete."
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
runBenchmark()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareResult(onScaffoldEvent: (ScaffoldEvent) -> Unit) {
|
||||||
|
_benchmarkResults.value.lastOrNull()?.let{
|
||||||
|
onScaffoldEvent(ScaffoldEvent.ShareText(it.text))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue