initial commit
This commit is contained in:
commit
04d294c65b
5
.gitignore
vendored
Executable file
5
.gitignore
vendored
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
target
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.vscode
|
||||||
|
.claude
|
||||||
6
config.properties
Normal file
6
config.properties
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#Llama Runner Configuration
|
||||||
|
#Mon Mar 23 12:24:12 CET 2026
|
||||||
|
windowHeight=854
|
||||||
|
windowWidth=587
|
||||||
|
windowX=1973
|
||||||
|
windowY=546
|
||||||
45
dependency-reduced-pom.xml
Normal file
45
dependency-reduced-pom.xml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>cz.kamma</groupId>
|
||||||
|
<artifactId>llama-runner</artifactId>
|
||||||
|
<name>Llama Runner</name>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<description>GUI application for running llama-server with customizable parameters</description>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>11</source>
|
||||||
|
<target>11</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<transformers>
|
||||||
|
<transformer>
|
||||||
|
<mainClass>cz.kamma.llamarunner.Main</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
</project>
|
||||||
62
pom.xml
Normal file
62
pom.xml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>cz.kamma</groupId>
|
||||||
|
<artifactId>llama-runner</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Llama Runner</name>
|
||||||
|
<description>GUI application for running llama-server with customizable parameters</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>11</maven.compiler.source>
|
||||||
|
<maven.compiler.target>11</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>2.10.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>11</source>
|
||||||
|
<target>11</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<mainClass>cz.kamma.llamarunner.Main</mainClass>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
97
profiles.json
Normal file
97
profiles.json
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"Nemotron Cascade Q8 160k": {
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 3080,
|
||||||
|
"parallel": 1,
|
||||||
|
"threads": 99,
|
||||||
|
"flashAttention": true,
|
||||||
|
"kvUnified": true,
|
||||||
|
"cacheTypeK": "q8_0",
|
||||||
|
"cacheTypeV": "q8_0",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"topP": 0.95,
|
||||||
|
"topK": 20,
|
||||||
|
"minP": 0.0,
|
||||||
|
"ctxSize": 160000,
|
||||||
|
"enableThinking": false,
|
||||||
|
"modelPath": "/home/kamma/models/Nemotron-Cascade-2-30B-A3B.Q8_0.gguf",
|
||||||
|
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||||
|
"ngl": -1
|
||||||
|
},
|
||||||
|
"Qwen3.5-q6k-180k": {
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 3080,
|
||||||
|
"parallel": 1,
|
||||||
|
"threads": 99,
|
||||||
|
"flashAttention": true,
|
||||||
|
"kvUnified": true,
|
||||||
|
"cacheTypeK": "q8_0",
|
||||||
|
"cacheTypeV": "q8_0",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"topP": 0.95,
|
||||||
|
"topK": 20,
|
||||||
|
"minP": 0.0,
|
||||||
|
"ctxSize": 180000,
|
||||||
|
"enableThinking": false,
|
||||||
|
"modelPath": "/home/kamma/models/Qwen3.5-35B-A3B-Q6_K.gguf",
|
||||||
|
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||||
|
"ngl": 999
|
||||||
|
},
|
||||||
|
"QwenCoderNext-160k": {
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 3080,
|
||||||
|
"parallel": 1,
|
||||||
|
"threads": 99,
|
||||||
|
"flashAttention": true,
|
||||||
|
"kvUnified": true,
|
||||||
|
"cacheTypeK": "q8_0",
|
||||||
|
"cacheTypeV": "q8_0",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"topP": 0.95,
|
||||||
|
"topK": 20,
|
||||||
|
"minP": 0.0,
|
||||||
|
"ctxSize": 160000,
|
||||||
|
"enableThinking": false,
|
||||||
|
"modelPath": "/home/kamma/models/Qwen3-Coder-Next-UD-Q2_K_XL.gguf",
|
||||||
|
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||||
|
"ngl": 999
|
||||||
|
},
|
||||||
|
"Nemotron Cascade 180k": {
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 3080,
|
||||||
|
"parallel": 1,
|
||||||
|
"threads": 99,
|
||||||
|
"flashAttention": true,
|
||||||
|
"kvUnified": true,
|
||||||
|
"cacheTypeK": "q8_0",
|
||||||
|
"cacheTypeV": "q8_0",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"topP": 0.95,
|
||||||
|
"topK": 20,
|
||||||
|
"minP": 0.0,
|
||||||
|
"ctxSize": 180000,
|
||||||
|
"enableThinking": false,
|
||||||
|
"modelPath": "/home/kamma/models/Nemotron-Cascade-2-30B-A3B.Q5_K_M.gguf",
|
||||||
|
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||||
|
"ngl": 999
|
||||||
|
},
|
||||||
|
"Qwen3.5 q6xl 160k": {
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 3080,
|
||||||
|
"parallel": 1,
|
||||||
|
"threads": 99,
|
||||||
|
"flashAttention": true,
|
||||||
|
"kvUnified": true,
|
||||||
|
"cacheTypeK": "q8_0",
|
||||||
|
"cacheTypeV": "q8_0",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"topP": 0.95,
|
||||||
|
"topK": 20,
|
||||||
|
"minP": 0.0,
|
||||||
|
"ctxSize": 160000,
|
||||||
|
"enableThinking": false,
|
||||||
|
"modelPath": "/home/kamma/models/Qwen3.5-35B-A3B-UD-Q6_K_XL.gguf",
|
||||||
|
"chatTemplateKwargs": "{\"enable_thinking\": false}",
|
||||||
|
"ngl": -1
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/main/java/cz/kamma/llamarunner/AppConfig.java
Normal file
102
src/main/java/cz/kamma/llamarunner/AppConfig.java
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package cz.kamma.llamarunner;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application configuration management for storing window size and position.
|
||||||
|
*/
|
||||||
|
public class AppConfig {
|
||||||
|
|
||||||
|
private static final int DEFAULT_WIDTH = 900;
|
||||||
|
private static final int DEFAULT_HEIGHT = 750;
|
||||||
|
private static final int DEFAULT_X = -1; // -1 means center on screen
|
||||||
|
private static final int DEFAULT_Y = -1;
|
||||||
|
|
||||||
|
private int windowWidth;
|
||||||
|
private int windowHeight;
|
||||||
|
private int windowX;
|
||||||
|
private int windowY;
|
||||||
|
private final ConfigLocation configLocation;
|
||||||
|
|
||||||
|
public AppConfig() {
|
||||||
|
this(new ConfigLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppConfig(ConfigLocation configLocation) {
|
||||||
|
this.configLocation = configLocation;
|
||||||
|
windowWidth = DEFAULT_WIDTH;
|
||||||
|
windowHeight = DEFAULT_HEIGHT;
|
||||||
|
windowX = DEFAULT_X;
|
||||||
|
windowY = DEFAULT_Y;
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void load() {
|
||||||
|
if (!configLocation.getConfigPropertiesFile().exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
try (FileInputStream fis = new FileInputStream(configLocation.getConfigPropertiesFile())) {
|
||||||
|
props.load(fis);
|
||||||
|
|
||||||
|
windowWidth = Integer.parseInt(props.getProperty("windowWidth", String.valueOf(DEFAULT_WIDTH)));
|
||||||
|
windowHeight = Integer.parseInt(props.getProperty("windowHeight", String.valueOf(DEFAULT_HEIGHT)));
|
||||||
|
windowX = Integer.parseInt(props.getProperty("windowX", String.valueOf(DEFAULT_X)));
|
||||||
|
windowY = Integer.parseInt(props.getProperty("windowY", String.valueOf(DEFAULT_Y)));
|
||||||
|
} catch (IOException | NumberFormatException e) {
|
||||||
|
// Use default values on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
configLocation.ensureDirectoryExists();
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.setProperty("windowWidth", String.valueOf(windowWidth));
|
||||||
|
props.setProperty("windowHeight", String.valueOf(windowHeight));
|
||||||
|
props.setProperty("windowX", String.valueOf(windowX));
|
||||||
|
props.setProperty("windowY", String.valueOf(windowY));
|
||||||
|
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(configLocation.getConfigPropertiesFile())) {
|
||||||
|
props.store(fos, "Llama Runner Configuration");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWindowWidth() {
|
||||||
|
return windowWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWindowWidth(int windowWidth) {
|
||||||
|
this.windowWidth = windowWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWindowHeight() {
|
||||||
|
return windowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWindowHeight(int windowHeight) {
|
||||||
|
this.windowHeight = windowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWindowX() {
|
||||||
|
return windowX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWindowX(int windowX) {
|
||||||
|
this.windowX = windowX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWindowY() {
|
||||||
|
return windowY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWindowY(int windowY) {
|
||||||
|
this.windowY = windowY;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/main/java/cz/kamma/llamarunner/CommandBuilder.java
Normal file
74
src/main/java/cz/kamma/llamarunner/CommandBuilder.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package cz.kamma.llamarunner;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds llama-server command lines from ModelConfig.
|
||||||
|
*/
|
||||||
|
public class CommandBuilder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the command string from a ModelConfig.
|
||||||
|
*
|
||||||
|
* @param config the model configuration
|
||||||
|
* @param modelsDirPath the path to the models directory
|
||||||
|
* @return the complete command string
|
||||||
|
*/
|
||||||
|
public String buildCommand(ModelConfig config, String modelsDirPath) {
|
||||||
|
StringBuilder cmd = new StringBuilder("llama-server");
|
||||||
|
cmd.append(" --host ").append(config.getHost());
|
||||||
|
cmd.append(" --port ").append(config.getPort());
|
||||||
|
cmd.append(" --parallel ").append(config.getParallel());
|
||||||
|
cmd.append(" -t ").append(config.getThreads());
|
||||||
|
cmd.append(" -fa ").append(config.isFlashAttention() ? "on" : "off");
|
||||||
|
cmd.append(" --temp ").append(config.getTemperature());
|
||||||
|
cmd.append(" --top-p ").append(config.getTopP());
|
||||||
|
cmd.append(" --top-k ").append(config.getTopK());
|
||||||
|
cmd.append(" --min-p ").append(config.getMinP());
|
||||||
|
|
||||||
|
if (config.isKvUnified()) {
|
||||||
|
cmd.append(" --kv-unified");
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.append(" --cache-type-k ").append(config.getCacheTypeK());
|
||||||
|
cmd.append(" --cache-type-v ").append(config.getCacheTypeV());
|
||||||
|
|
||||||
|
cmd.append(" --ctx-size ").append(config.getCtxSize());
|
||||||
|
|
||||||
|
if (!config.isFit()) {
|
||||||
|
cmd.append(" -ngl ").append(config.getNgl());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isFit()) {
|
||||||
|
cmd.append(" --fit on");
|
||||||
|
}
|
||||||
|
|
||||||
|
String modelPath = buildModelPath(config.getModelPath(), modelsDirPath);
|
||||||
|
cmd.append(" -m ").append(modelPath);
|
||||||
|
|
||||||
|
String kwargsText = config.getChatTemplateKwargs();
|
||||||
|
if (kwargsText != null && !kwargsText.trim().isEmpty()) {
|
||||||
|
cmd.append(" --chat-template-kwargs \"");
|
||||||
|
cmd.append(kwargsText.replace("\"", "\\\""));
|
||||||
|
cmd.append("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the full model path.
|
||||||
|
*/
|
||||||
|
private String buildModelPath(String modelPath, String modelsDirPath) {
|
||||||
|
if (modelPath == null || modelPath.trim().isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
File modelFile = new File(modelPath);
|
||||||
|
if (modelFile.isAbsolute()) {
|
||||||
|
return modelPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new File(modelsDirPath, modelPath).getAbsolutePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/main/java/cz/kamma/llamarunner/ConfigLocation.java
Normal file
48
src/main/java/cz/kamma/llamarunner/ConfigLocation.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package cz.kamma.llamarunner;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages configuration file locations in the user's home directory.
|
||||||
|
*/
|
||||||
|
public class ConfigLocation {
|
||||||
|
|
||||||
|
private final File appDir;
|
||||||
|
private final File configPropertiesFile;
|
||||||
|
private final File profilesJsonFile;
|
||||||
|
|
||||||
|
public ConfigLocation() {
|
||||||
|
appDir = new File(".");
|
||||||
|
configPropertiesFile = new File(appDir, "config.properties");
|
||||||
|
profilesJsonFile = new File(appDir, "profiles.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the application directory exists.
|
||||||
|
*/
|
||||||
|
public void ensureDirectoryExists() {
|
||||||
|
if (!appDir.exists()) {
|
||||||
|
appDir.mkdirs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getAppDir() {
|
||||||
|
return appDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getConfigPropertiesFile() {
|
||||||
|
return configPropertiesFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getProfilesJsonFile() {
|
||||||
|
return profilesJsonFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConfigPropertiesFilePath() {
|
||||||
|
return configPropertiesFile.getAbsolutePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProfilesJsonFilePath() {
|
||||||
|
return profilesJsonFile.getAbsolutePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/main/java/cz/kamma/llamarunner/ConfigValidation.java
Normal file
123
src/main/java/cz/kamma/llamarunner/ConfigValidation.java
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package cz.kamma.llamarunner;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation utilities for configuration inputs.
|
||||||
|
*/
|
||||||
|
public class ConfigValidation {
|
||||||
|
|
||||||
|
private static final Gson GSON = new Gson();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates numeric fields for valid ranges.
|
||||||
|
*
|
||||||
|
* @return a list of error messages (empty if valid)
|
||||||
|
*/
|
||||||
|
public static java.util.List<String> validateNumericInputs(
|
||||||
|
String host,
|
||||||
|
int port,
|
||||||
|
int parallel,
|
||||||
|
int threads,
|
||||||
|
double temperature,
|
||||||
|
double topP,
|
||||||
|
int topK,
|
||||||
|
double minP,
|
||||||
|
int ctxSize,
|
||||||
|
int ngl) {
|
||||||
|
|
||||||
|
java.util.List<String> errors = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
// Validate port
|
||||||
|
if (port < 1 || port > 65535) {
|
||||||
|
errors.add("Port must be between 1 and 65535");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate parallel
|
||||||
|
if (parallel < 1) {
|
||||||
|
errors.add("Parallel must be at least 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate threads
|
||||||
|
if (threads < 1) {
|
||||||
|
errors.add("Threads must be at least 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate temperature
|
||||||
|
if (temperature < 0) {
|
||||||
|
errors.add("Temperature must be non-negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate topP
|
||||||
|
if (topP < 0 || topP > 1) {
|
||||||
|
errors.add("Top P must be between 0 and 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate topK
|
||||||
|
if (topK < 0) {
|
||||||
|
errors.add("Top K must be non-negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate minP
|
||||||
|
if (minP < 0 || minP > 1) {
|
||||||
|
errors.add("Min P must be between 0 and 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate ctxSize
|
||||||
|
if (ctxSize < 0) {
|
||||||
|
errors.add("Context size must be non-negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate ngl
|
||||||
|
if (ngl < 0) {
|
||||||
|
errors.add("GPU layers must be non-negative");
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates JSON string for chatTemplateKwargs.
|
||||||
|
*
|
||||||
|
* @param json the JSON string to validate
|
||||||
|
* @return error message if invalid, null if valid
|
||||||
|
*/
|
||||||
|
public static String validateJsonKwargs(String json) {
|
||||||
|
if (json == null || json.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
GSON.fromJson(json, Object.class);
|
||||||
|
return null;
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
return "Invalid JSON format in kwargs";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates model path exists.
|
||||||
|
*
|
||||||
|
* @param modelPath the model file path
|
||||||
|
* @return error message if file doesn't exist, null if valid
|
||||||
|
*/
|
||||||
|
public static String validateModelPath(String modelPath) {
|
||||||
|
if (modelPath == null || modelPath.trim().isEmpty()) {
|
||||||
|
return "Model path cannot be empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
File modelFile = new File(modelPath);
|
||||||
|
if (!modelFile.exists()) {
|
||||||
|
return "Model file does not exist: " + modelPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modelFile.isFile()) {
|
||||||
|
return "Model path is not a file: " + modelPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
1030
src/main/java/cz/kamma/llamarunner/Main.java
Normal file
1030
src/main/java/cz/kamma/llamarunner/Main.java
Normal file
File diff suppressed because it is too large
Load Diff
129
src/main/java/cz/kamma/llamarunner/ModelConfig.java
Normal file
129
src/main/java/cz/kamma/llamarunner/ModelConfig.java
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package cz.kamma.llamarunner;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for storing llama-server configuration.
|
||||||
|
*/
|
||||||
|
public class ModelConfig implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String host;
|
||||||
|
private int port;
|
||||||
|
private int parallel;
|
||||||
|
private int threads;
|
||||||
|
private boolean flashAttention;
|
||||||
|
private boolean kvUnified;
|
||||||
|
private String cacheTypeK;
|
||||||
|
private String cacheTypeV;
|
||||||
|
private double temperature;
|
||||||
|
private double topP;
|
||||||
|
private int topK;
|
||||||
|
private double minP;
|
||||||
|
private int ctxSize;
|
||||||
|
private boolean enableThinking;
|
||||||
|
private String modelPath;
|
||||||
|
private String chatTemplateKwargs;
|
||||||
|
private int ngl;
|
||||||
|
private boolean fit;
|
||||||
|
|
||||||
|
public ModelConfig() {
|
||||||
|
this.host = "0.0.0.0";
|
||||||
|
this.port = 3080;
|
||||||
|
this.parallel = 1;
|
||||||
|
this.threads = 99;
|
||||||
|
this.flashAttention = true;
|
||||||
|
this.kvUnified = true;
|
||||||
|
this.cacheTypeK = "bf16";
|
||||||
|
this.cacheTypeV = "bf16";
|
||||||
|
this.temperature = 0.6;
|
||||||
|
this.topP = 0.95;
|
||||||
|
this.topK = 20;
|
||||||
|
this.minP = 0.00;
|
||||||
|
this.ctxSize = 180000;
|
||||||
|
this.enableThinking = false;
|
||||||
|
this.modelPath = "";
|
||||||
|
this.chatTemplateKwargs = "";
|
||||||
|
this.ngl = 999;
|
||||||
|
this.fit = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and setters
|
||||||
|
public String getHost() { return host; }
|
||||||
|
public void setHost(String host) { this.host = host; }
|
||||||
|
|
||||||
|
public int getPort() { return port; }
|
||||||
|
public void setPort(int port) { this.port = port; }
|
||||||
|
|
||||||
|
public int getParallel() { return parallel; }
|
||||||
|
public void setParallel(int parallel) { this.parallel = parallel; }
|
||||||
|
|
||||||
|
public int getThreads() { return threads; }
|
||||||
|
public void setThreads(int threads) { this.threads = threads; }
|
||||||
|
|
||||||
|
public boolean isFlashAttention() { return flashAttention; }
|
||||||
|
public void setFlashAttention(boolean flashAttention) { this.flashAttention = flashAttention; }
|
||||||
|
|
||||||
|
public boolean isKvUnified() { return kvUnified; }
|
||||||
|
public void setKvUnified(boolean kvUnified) { this.kvUnified = kvUnified; }
|
||||||
|
|
||||||
|
public String getCacheTypeK() { return cacheTypeK; }
|
||||||
|
public void setCacheTypeK(String cacheTypeK) { this.cacheTypeK = cacheTypeK; }
|
||||||
|
|
||||||
|
public String getCacheTypeV() { return cacheTypeV; }
|
||||||
|
public void setCacheTypeV(String cacheTypeV) { this.cacheTypeV = cacheTypeV; }
|
||||||
|
|
||||||
|
public double getTemperature() { return temperature; }
|
||||||
|
public void setTemperature(double temperature) { this.temperature = temperature; }
|
||||||
|
|
||||||
|
public double getTopP() { return topP; }
|
||||||
|
public void setTopP(double topP) { this.topP = topP; }
|
||||||
|
|
||||||
|
public int getTopK() { return topK; }
|
||||||
|
public void setTopK(int topK) { this.topK = topK; }
|
||||||
|
|
||||||
|
public double getMinP() { return minP; }
|
||||||
|
public void setMinP(double minP) { this.minP = minP; }
|
||||||
|
|
||||||
|
public int getCtxSize() { return ctxSize; }
|
||||||
|
public void setCtxSize(int ctxSize) { this.ctxSize = ctxSize; }
|
||||||
|
|
||||||
|
public boolean isEnableThinking() { return enableThinking; }
|
||||||
|
public void setEnableThinking(boolean enableThinking) { this.enableThinking = enableThinking; }
|
||||||
|
|
||||||
|
public String getModelPath() { return modelPath; }
|
||||||
|
public void setModelPath(String modelPath) { this.modelPath = modelPath; }
|
||||||
|
|
||||||
|
public String getChatTemplateKwargs() { return chatTemplateKwargs; }
|
||||||
|
public void setChatTemplateKwargs(String chatTemplateKwargs) { this.chatTemplateKwargs = chatTemplateKwargs; }
|
||||||
|
|
||||||
|
public int getNgl() { return ngl; }
|
||||||
|
public void setNgl(int ngl) { this.ngl = ngl; }
|
||||||
|
|
||||||
|
public boolean isFit() { return fit; }
|
||||||
|
public void setFit(boolean fit) { this.fit = fit; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ModelConfig{" +
|
||||||
|
"host='" + host + '\'' +
|
||||||
|
", port=" + port +
|
||||||
|
", parallel=" + parallel +
|
||||||
|
", threads=" + threads +
|
||||||
|
", flashAttention=" + flashAttention +
|
||||||
|
", kvUnified=" + kvUnified +
|
||||||
|
", cacheTypeK='" + cacheTypeK + '\'' +
|
||||||
|
", cacheTypeV='" + cacheTypeV + '\'' +
|
||||||
|
", temperature=" + temperature +
|
||||||
|
", topP=" + topP +
|
||||||
|
", topK=" + topK +
|
||||||
|
", minP=" + minP +
|
||||||
|
", ctxSize=" + ctxSize +
|
||||||
|
", enableThinking=" + enableThinking +
|
||||||
|
", modelPath='" + modelPath + '\'' +
|
||||||
|
", chatTemplateKwargs='" + chatTemplateKwargs + '\'' +
|
||||||
|
", ngl=" + ngl +
|
||||||
|
", fit=" + fit +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/main/java/cz/kamma/llamarunner/ProfileManager.java
Normal file
137
src/main/java/cz/kamma/llamarunner/ProfileManager.java
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package cz.kamma.llamarunner;
|
||||||
|
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile management using JSON file in user's home directory.
|
||||||
|
*/
|
||||||
|
public class ProfileManager {
|
||||||
|
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
private final ConfigLocation configLocation;
|
||||||
|
|
||||||
|
public ProfileManager() {
|
||||||
|
this(new ConfigLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProfileManager(ConfigLocation configLocation) {
|
||||||
|
this.configLocation = configLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves profile to JSON file.
|
||||||
|
*/
|
||||||
|
public void saveProfile(String name, ModelConfig config) throws IOException {
|
||||||
|
configLocation.ensureDirectoryExists();
|
||||||
|
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||||
|
profiles.put(name, config);
|
||||||
|
|
||||||
|
try (FileWriter writer = new FileWriter(configLocation.getProfilesJsonFile())) {
|
||||||
|
GSON.toJson(profiles, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads profile from JSON file.
|
||||||
|
*/
|
||||||
|
public ModelConfig loadProfile(String name) throws IOException {
|
||||||
|
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||||
|
ModelConfig config = profiles.get(name);
|
||||||
|
if (config == null) {
|
||||||
|
throw new IOException("Profile does not exist: " + name);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes profile from JSON file.
|
||||||
|
*/
|
||||||
|
public void deleteProfile(String name) throws IOException {
|
||||||
|
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||||
|
if (!profiles.containsKey(name)) {
|
||||||
|
throw new IOException("Profile does not exist: " + name);
|
||||||
|
}
|
||||||
|
profiles.remove(name);
|
||||||
|
|
||||||
|
try (FileWriter writer = new FileWriter(configLocation.getProfilesJsonFile())) {
|
||||||
|
GSON.toJson(profiles, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path to the profiles file.
|
||||||
|
*/
|
||||||
|
public String getProfilesFilePath() {
|
||||||
|
return configLocation.getProfilesJsonFilePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of profile names.
|
||||||
|
*/
|
||||||
|
public List<String> listProfiles() throws IOException {
|
||||||
|
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||||
|
return profiles != null ? new ArrayList<>(profiles.keySet()) : new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if profile exists.
|
||||||
|
*/
|
||||||
|
public boolean profileExists(String name) {
|
||||||
|
try {
|
||||||
|
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||||
|
return profiles.containsKey(name);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renames a profile from oldName to newName.
|
||||||
|
*/
|
||||||
|
public void renameProfile(String oldName, String newName) throws IOException {
|
||||||
|
Map<String, ModelConfig> profiles = loadAllProfiles();
|
||||||
|
if (!profiles.containsKey(oldName)) {
|
||||||
|
throw new IOException("Profile does not exist: " + oldName);
|
||||||
|
}
|
||||||
|
if (oldName.equals(newName)) {
|
||||||
|
throw new IOException("Old and new names must be different.");
|
||||||
|
}
|
||||||
|
if (profiles.containsKey(newName)) {
|
||||||
|
throw new IOException("Profile already exists: " + newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelConfig config = profiles.remove(oldName);
|
||||||
|
profiles.put(newName, config);
|
||||||
|
|
||||||
|
try (FileWriter writer = new FileWriter(configLocation.getProfilesJsonFile())) {
|
||||||
|
GSON.toJson(profiles, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all profiles from JSON file.
|
||||||
|
*/
|
||||||
|
private Map<String, ModelConfig> loadAllProfiles() throws IOException {
|
||||||
|
if (!configLocation.getProfilesJsonFile().exists()) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (FileReader reader = new FileReader(configLocation.getProfilesJsonFile())) {
|
||||||
|
java.lang.reflect.Type mapType = new TypeToken<Map<String, ModelConfig>>() {}.getType();
|
||||||
|
Map<String, ModelConfig> profiles = GSON.fromJson(reader, mapType);
|
||||||
|
return profiles != null ? profiles : new HashMap<>();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/main/java/cz/kamma/llamarunner/ProfileValidator.java
Normal file
51
src/main/java/cz/kamma/llamarunner/ProfileValidator.java
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package cz.kamma.llamarunner;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation utilities for profile names.
|
||||||
|
*/
|
||||||
|
public class ProfileValidator {
|
||||||
|
|
||||||
|
private static final int MAX_NAME_LENGTH = 100;
|
||||||
|
private static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a profile name.
|
||||||
|
*
|
||||||
|
* @param name the profile name to validate
|
||||||
|
* @return error message if invalid, null if valid
|
||||||
|
*/
|
||||||
|
public static String validateProfileName(String name) {
|
||||||
|
if (name == null || name.trim().isEmpty()) {
|
||||||
|
return "Profile name cannot be empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
String trimmedName = name.trim();
|
||||||
|
|
||||||
|
if (trimmedName.length() > MAX_NAME_LENGTH) {
|
||||||
|
return "Profile name must be at most " + MAX_NAME_LENGTH + " characters";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!VALID_NAME_PATTERN.matcher(trimmedName).matches()) {
|
||||||
|
return "Profile name can only contain letters, numbers, underscores, and hyphens";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a profile name is unique among existing profiles.
|
||||||
|
*
|
||||||
|
* @param newName the new profile name
|
||||||
|
* @param existingProfiles list of existing profile names
|
||||||
|
* @return true if unique, false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean isProfileNameUnique(String newName, java.util.List<String> existingProfiles) {
|
||||||
|
if (existingProfiles == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !existingProfiles.stream()
|
||||||
|
.anyMatch(name -> name.equalsIgnoreCase(newName));
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/main/resources/llama.png
Normal file
BIN
src/main/resources/llama.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Loading…
x
Reference in New Issue
Block a user