diff --git a/README.md b/README.md index 9c7fe24..55d0bd3 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,73 @@ # lm-launcher -**Program Name:** Machine Learning Model and Application Manager +A small Swing GUI for managing local GGUF models and launching apps with model-dependent command-line placeholders. -**Purpose:** The program is a graphical user interface (GUI) application that allows users to manage machine learning models and applications. It provides a user-friendly interface for adding, editing, deleting, and cloning models and applications, as well as running applications with selected models. +## What it does -**Key Features:** +- scans a configured models directory for `.gguf` files +- stores per-model settings in `models.json` +- stores app launch definitions in `apps.json` +- launches apps with placeholders resolved from the selected model -1. **Model Management:** Users can add, edit, delete, and clone models, as well as view their properties. -2. **Application Management:** Users can add, edit, delete, and clone applications, as well as view their properties and run them with a selected model. -3. **Data Storage:** The program uses a data storage system that loads and manages data from two JSON files: "models.json" and "apps.json". -4. **GUI Components:** The program uses various GUI components, such as buttons, labels, sliders, and tables, to provide a user-friendly interface. -5. **Event Handling:** The program uses event listeners to handle user interactions, such as button clicks and table selections. -6. **Data Refresh:** The program has a method called `refresh()` that updates the GUI components with the latest data from the data storage. -7. **Model and Application Editing:** Users can edit the properties of models and applications using dedicated dialog windows. -8. **Argument Management:** Users can add, edit, or remove rows in a table to manage application arguments, and the changes are reflected in the application's arguments. +## Model settings -**Overall:** The program provides a comprehensive GUI application for managing machine learning models and applications, making it easier for users to work with these components and run applications with selected models. +Each model can store: -*[requests=10, requestDuration=PT25.84142404S, inTokens=10623, outTokens=1240, tps=49.6]* +- context length +- max context length +- threads +- GPU layers +- prompt template +- temperature +- top-p +- top-k +- min-p + +The main window also provides sliders for quickly adjusting launch-time values for: + +- context length +- threads +- GPU layers +- temperature +- top-p +- top-k +- min-p + +## App placeholders + +Use these tokens in app arguments: + +- `MODEL` +- `CONTEXT_SIZE` +- `THREADS` +- `GPU_LAYERS` +- `TEMP` +- `TOP_P` +- `TOP_K` +- `MIN_P` + +Example: + +```text +-m MODEL -c CONTEXT_SIZE -t THREADS -ngl GPU_LAYERS --temp TEMP --top-p TOP_P --top-k TOP_K --min-p MIN_P +``` + +## Files + +- `lm-launcher.properties` – launcher configuration +- `models.json` – saved model settings +- `apps.json` – saved app definitions +- `run.sh` – starts the packaged launcher jar + +## Run + +```bash +./run.sh +``` + +To build from source with the bundled Jackson jars: + +```bash +javac -cp jackson-annotations-2.17.2.jar:jackson-core-2.17.2.jar:jackson-databind-2.17.2.jar -d /tmp/lm-launcher-build $(find src -name '*.java' | sort) +java -cp /tmp/lm-launcher-build:jackson-annotations-2.17.2.jar:jackson-core-2.17.2.jar:jackson-databind-2.17.2.jar de.extio.lm_launcher.Main +``` \ No newline at end of file diff --git a/apps.json b/apps.json index 7c3acac..2ca1a4c 100644 --- a/apps.json +++ b/apps.json @@ -1 +1 @@ -{"apps":[{"path":"file:///home/sb/lm/koboldcpp-rocm/koboldcpp.py","interpreter":"file:///usr/bin/python","arguments":[],"appArguments":[{"argument":"--usecublas","optional":true,"default_":true},{"argument":"mmq","optional":true,"default_":false},{"argument":"lowvram","optional":true,"default_":false},{"argument":"0","optional":true,"default_":true},{"argument":"--gpulayers","optional":true,"default_":true},{"argument":"GPU_LAYERS","optional":true,"default_":true},{"argument":"--smartcontext","optional":true,"default_":false},{"argument":"--noshift","optional":true,"default_":false},{"argument":"--contextsize","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"--launch","optional":false,"default_":true},{"argument":"--host","optional":true,"default_":false},{"argument":"0.0.0.0","optional":true,"default_":false},{"argument":"--threads","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"--model","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}]},{"path":"file:///home/sb/lm/llama.cpp/llama-server","interpreter":null,"arguments":[],"appArguments":[{"argument":"-nkvo","optional":true,"default_":false},{"argument":"--parallel","optional":true,"default_":false},{"argument":"2","optional":true,"default_":false},{"argument":"4","optional":true,"default_":false},{"argument":"--host","optional":true,"default_":true},{"argument":"0.0.0.0","optional":true,"default_":true},{"argument":"--port","optional":false,"default_":true},{"argument":"5001","optional":false,"default_":true},{"argument":"-t","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"-ngl","optional":false,"default_":true},{"argument":"GPU_LAYERS","optional":false,"default_":true},{"argument":"--ctx-size","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"-m","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}]},{"path":"file:///home/sb/lm/SillyTavern/start.sh","interpreter":null,"arguments":[],"appArguments":[]}]} +{"apps":[{"path":"file:///home/sb/lm/koboldcpp-rocm/koboldcpp.py","interpreter":"file:///usr/bin/python","arguments":[],"appArguments":[{"argument":"--usecublas","optional":true,"default_":true},{"argument":"mmq","optional":true,"default_":false},{"argument":"lowvram","optional":true,"default_":false},{"argument":"0","optional":true,"default_":true},{"argument":"--gpulayers","optional":true,"default_":true},{"argument":"GPU_LAYERS","optional":true,"default_":true},{"argument":"--smartcontext","optional":true,"default_":false},{"argument":"--noshift","optional":true,"default_":false},{"argument":"--contextsize","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"--launch","optional":false,"default_":true},{"argument":"--host","optional":true,"default_":false},{"argument":"0.0.0.0","optional":true,"default_":false},{"argument":"--threads","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"--model","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}]},{"path":"file:///home/sb/lm/llama.cpp/llama-server","interpreter":null,"arguments":[],"appArguments":[{"argument":"-nkvo","optional":true,"default_":false},{"argument":"--parallel","optional":true,"default_":false},{"argument":"2","optional":true,"default_":false},{"argument":"4","optional":true,"default_":false},{"argument":"--host","optional":true,"default_":true},{"argument":"0.0.0.0","optional":true,"default_":true},{"argument":"--port","optional":false,"default_":true},{"argument":"5001","optional":false,"default_":true},{"argument":"-t","optional":false,"default_":true},{"argument":"THREADS","optional":false,"default_":true},{"argument":"-ngl","optional":false,"default_":true},{"argument":"GPU_LAYERS","optional":false,"default_":true},{"argument":"--ctx-size","optional":false,"default_":true},{"argument":"CONTEXT_SIZE","optional":false,"default_":true},{"argument":"-m","optional":false,"default_":true},{"argument":"MODEL","optional":false,"default_":true}]},{"path":"file:///home/sb/lm/SillyTavern/start.sh","interpreter":null,"arguments":[],"appArguments":[]},{"path":"file:///home/sb/lm/ollama-oai-bridge/start.sh","interpreter":null,"arguments":[],"appArguments":[]}]} \ No newline at end of file diff --git a/lm-launcher.jar b/lm-launcher.jar index 0996152..8013ace 100644 Binary files a/lm-launcher.jar and b/lm-launcher.jar differ diff --git a/src/de/extio/lm_launcher/App.java b/src/de/extio/lm_launcher/App.java index 76fea95..928d2c3 100644 --- a/src/de/extio/lm_launcher/App.java +++ b/src/de/extio/lm_launcher/App.java @@ -15,7 +15,11 @@ record App(Path path, Path interpreter, @Deprecated List arguments, List "MODEL", m -> m.path(), "CONTEXT_SIZE", m -> String.valueOf(m.contextSize()), "THREADS", m -> String.valueOf(m.threads()), - "GPU_LAYERS", m -> String.valueOf(m.gpuLayers())); + "GPU_LAYERS", m -> String.valueOf(m.gpuLayers()), + "TEMP", Model::temperatureDisplay, + "TOP_P", Model::topPDisplay, + "TOP_K", Model::topKDisplay, + "MIN_P", Model::minPDisplay); String argumentsToString(boolean showAll) { return this.argumentsToStringByArgs(this.appArguments.stream(), showAll); diff --git a/src/de/extio/lm_launcher/Data.java b/src/de/extio/lm_launcher/Data.java index bd6e35a..9b064d4 100644 --- a/src/de/extio/lm_launcher/Data.java +++ b/src/de/extio/lm_launcher/Data.java @@ -10,7 +10,6 @@ import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Objects; import java.util.Properties; import com.fasterxml.jackson.databind.ObjectMapper; @@ -46,8 +45,10 @@ public class Data { // Migration for (int i = 0; i < modelData.models().size(); i++) { final Model model = modelData.models.get(i); - if (model.maxContextSize() < model.contextSize() || model.promptTemplate() == null) { - modelData.models.set(i, new Model(model.path(), model.contextSize(), model.contextSize(), model.gpuLayers(), model.threads(), Objects.requireNonNullElse(model.promptTemplate(), ""), model.ctime())); + if (model.maxContextSize() < model.contextSize() || model.promptTemplate() == null || model.temperature() == null || model.topP() == null || model.topK() == null + || model.minP() == null) { + modelData.models.set(i, new Model(model.path(), model.contextSize(), model.maxContextSize(), model.gpuLayers(), model.threads(), model.temperature(), model.topP(), model.topK(), + model.minP(), model.promptTemplate(), model.ctime())); } } sortModels(); @@ -154,7 +155,7 @@ public class Data { catch (final IOException e) { // Use 0 as fallback } - return new Model(path, 16000, 128000, 99, 6, "", ctime); + return new Model(path, 16000, 128000, 99, 6, Model.DEFAULT_TEMPERATURE, Model.DEFAULT_TOP_P, Model.DEFAULT_TOP_K, Model.DEFAULT_MIN_P, "", ctime); } static record ModelData(List models) { diff --git a/src/de/extio/lm_launcher/LaunchFrame.java b/src/de/extio/lm_launcher/LaunchFrame.java index 3f85704..1191a93 100644 --- a/src/de/extio/lm_launcher/LaunchFrame.java +++ b/src/de/extio/lm_launcher/LaunchFrame.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; +import java.util.Hashtable; import java.util.List; import java.util.Map; @@ -32,6 +33,7 @@ import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTree; import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; import javax.swing.WindowConstants; import javax.swing.border.EmptyBorder; @@ -48,6 +50,8 @@ public class LaunchFrame extends JFrame { private static final long serialVersionUID = 1L; + private static final int DECIMAL_SLIDER_SCALE = 100; + private final JPanel contentPane; JPanel panel; @@ -125,6 +129,30 @@ public class LaunchFrame extends JFrame { JLabel lblPromptTemplate_1; + JLabel lblTemperature; + + JSlider slider_temperature; + + JLabel lblTemperatureValue; + + JLabel lblTopP; + + JSlider slider_topP; + + JLabel lblTopPValue; + + JLabel lblTopK; + + JSlider slider_topK; + + JLabel lblTopKValue; + + JLabel lblMinP; + + JSlider slider_minP; + + JLabel lblMinPValue; + /** * Create the frame. */ @@ -255,8 +283,12 @@ public class LaunchFrame extends JFrame { this.scrollPane_1.setViewportView(this.modelTree); { + final JScrollPane modelSettingsScrollPane = new JScrollPane(); + modelSettingsScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + modelSettingsScrollPane.getVerticalScrollBar().setUnitIncrement(UIScaler.scale(16)); + this.splitPane_2.setRightComponent(modelSettingsScrollPane); this.panel_3 = new JPanel(); - this.splitPane_2.setRightComponent(this.panel_3); + modelSettingsScrollPane.setViewportView(this.panel_3); this.panel_3.setLayout(new GridLayout(0, 1, 0, 0)); { this.lblContextLength = new JLabel("Context Length"); @@ -306,6 +338,7 @@ public class LaunchFrame extends JFrame { } { this.lblPromptTemplate = new JLabel("Prompt Template"); + this.lblPromptTemplate.setHorizontalAlignment(SwingConstants.CENTER); this.panel_3.add(this.lblPromptTemplate); } { @@ -313,6 +346,98 @@ public class LaunchFrame extends JFrame { this.lblPromptTemplate_1.setHorizontalAlignment(SwingConstants.CENTER); this.panel_3.add(this.lblPromptTemplate_1); } + { + this.lblTemperature = new JLabel("Temperature"); + this.lblTemperature.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblTemperature); + } + { + this.slider_temperature = new JSlider(); + this.slider_temperature.setMinimum((int) (Model.MIN_TEMPERATURE * DECIMAL_SLIDER_SCALE)); + this.slider_temperature.setMaximum((int) (Model.MAX_TEMPERATURE * DECIMAL_SLIDER_SCALE)); + this.slider_temperature.setValue(LaunchFrame.doubleToSlider(Model.DEFAULT_TEMPERATURE)); + this.slider_temperature.setMajorTickSpacing(50); + this.slider_temperature.setMinorTickSpacing(5); + this.slider_temperature.setLabelTable(LaunchFrame.createDecimalLabelTable(new int[] { 0, 50, 100, 150, 200 })); + this.slider_temperature.setPaintLabels(true); + this.slider_temperature.setPaintTicks(true); + this.slider_temperature.addChangeListener(e -> LaunchFrame.this.updateSamplingValueLabels()); + this.panel_3.add(this.slider_temperature); + } + { + this.lblTemperatureValue = new JLabel("-"); + this.lblTemperatureValue.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblTemperatureValue); + } + { + this.lblTopP = new JLabel("Top P"); + this.lblTopP.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblTopP); + } + { + this.slider_topP = new JSlider(); + this.slider_topP.setMinimum((int) (Model.MIN_TOP_P * DECIMAL_SLIDER_SCALE)); + this.slider_topP.setMaximum((int) (Model.MAX_TOP_P * DECIMAL_SLIDER_SCALE)); + this.slider_topP.setValue(LaunchFrame.doubleToSlider(Model.DEFAULT_TOP_P)); + this.slider_topP.setMajorTickSpacing(25); + this.slider_topP.setMinorTickSpacing(5); + this.slider_topP.setLabelTable(LaunchFrame.createDecimalLabelTable(new int[] { 0, 25, 50, 75, 100 })); + this.slider_topP.setPaintLabels(true); + this.slider_topP.setPaintTicks(true); + this.slider_topP.addChangeListener(e -> LaunchFrame.this.updateSamplingValueLabels()); + this.panel_3.add(this.slider_topP); + } + { + this.lblTopPValue = new JLabel("-"); + this.lblTopPValue.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblTopPValue); + } + { + this.lblTopK = new JLabel("Top K"); + this.lblTopK.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblTopK); + } + { + this.slider_topK = new JSlider(); + this.slider_topK.setMinimum(Model.MIN_TOP_K); + this.slider_topK.setMaximum(Model.MAX_TOP_K); + this.slider_topK.setValue(Model.DEFAULT_TOP_K); + this.slider_topK.setMajorTickSpacing(100); + this.slider_topK.setMinorTickSpacing(25); + this.slider_topK.setSnapToTicks(true); + this.slider_topK.setPaintLabels(true); + this.slider_topK.setPaintTicks(true); + this.slider_topK.addChangeListener(e -> LaunchFrame.this.updateSamplingValueLabels()); + this.panel_3.add(this.slider_topK); + } + { + this.lblTopKValue = new JLabel("-"); + this.lblTopKValue.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblTopKValue); + } + { + this.lblMinP = new JLabel("Min P"); + this.lblMinP.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblMinP); + } + { + this.slider_minP = new JSlider(); + this.slider_minP.setMinimum((int) (Model.MIN_MIN_P * DECIMAL_SLIDER_SCALE)); + this.slider_minP.setMaximum((int) (Model.MAX_MIN_P * DECIMAL_SLIDER_SCALE)); + this.slider_minP.setValue(LaunchFrame.doubleToSlider(Model.DEFAULT_MIN_P)); + this.slider_minP.setMajorTickSpacing(25); + this.slider_minP.setMinorTickSpacing(5); + this.slider_minP.setLabelTable(LaunchFrame.createDecimalLabelTable(new int[] { 0, 25, 50, 75, 100 })); + this.slider_minP.setPaintLabels(true); + this.slider_minP.setPaintTicks(true); + this.slider_minP.addChangeListener(e -> LaunchFrame.this.updateSamplingValueLabels()); + this.panel_3.add(this.slider_minP); + } + { + this.lblMinPValue = new JLabel("-"); + this.lblMinPValue.setHorizontalAlignment(SwingConstants.CENTER); + this.panel_3.add(this.lblMinPValue); + } { this.verticalStrut = Box.createVerticalStrut(20); this.panel_3.add(this.verticalStrut); @@ -448,6 +573,10 @@ public class LaunchFrame extends JFrame { template.maxContextSize(), LaunchFrame.this.slider_gpu.getValue(), LaunchFrame.this.slider_threads.getValue(), + LaunchFrame.this.sliderValue(LaunchFrame.this.slider_temperature), + LaunchFrame.this.sliderValue(LaunchFrame.this.slider_topP), + LaunchFrame.this.slider_topK.getValue(), + LaunchFrame.this.sliderValue(LaunchFrame.this.slider_minP), template.promptTemplate(), template.ctime()); } @@ -484,6 +613,7 @@ public class LaunchFrame extends JFrame { UIScaler.scaleComponentFonts(this); UIScaler.centerOnPrimaryDisplay(this); + this.updateSamplingValueLabels(); this.repaint(); this.splitPane.setDividerLocation(0.33); this.splitPane_1.setDividerLocation(0.66); @@ -512,7 +642,35 @@ public class LaunchFrame extends JFrame { this.slider_threads.setValue(model.threads()); this.slider_gpu.setValue(model.gpuLayers()); + this.slider_temperature.setValue(LaunchFrame.doubleToSlider(model.temperatureValue())); + this.slider_topP.setValue(LaunchFrame.doubleToSlider(model.topPValue())); + this.slider_topK.setValue(model.topKValue()); + this.slider_minP.setValue(LaunchFrame.doubleToSlider(model.minPValue())); this.lblPromptTemplate_1.setText(model.promptTemplate() != null && !model.promptTemplate().isEmpty() ? model.promptTemplate() : "-"); + this.updateSamplingValueLabels(); + } + + private void updateSamplingValueLabels() { + this.lblTemperatureValue.setText(Model.formatDecimal(this.sliderValue(this.slider_temperature))); + this.lblTopPValue.setText(Model.formatDecimal(this.sliderValue(this.slider_topP))); + this.lblTopKValue.setText(String.valueOf(this.slider_topK.getValue())); + this.lblMinPValue.setText(Model.formatDecimal(this.sliderValue(this.slider_minP))); + } + + private double sliderValue(final JSlider slider) { + return slider.getValue() / (double) DECIMAL_SLIDER_SCALE; + } + + private static int doubleToSlider(final double value) { + return (int) Math.round(value * DECIMAL_SLIDER_SCALE); + } + + private static Hashtable createDecimalLabelTable(final int[] values) { + final Hashtable labels = new Hashtable<>(); + for (final int value : values) { + labels.put(value, new JLabel(Model.formatDecimal(value / (double) DECIMAL_SLIDER_SCALE))); + } + return labels; } private void refresh() { diff --git a/src/de/extio/lm_launcher/Model.java b/src/de/extio/lm_launcher/Model.java index 089b959..f91a37f 100644 --- a/src/de/extio/lm_launcher/Model.java +++ b/src/de/extio/lm_launcher/Model.java @@ -1,4 +1,97 @@ package de.extio.lm_launcher; -record Model(String path, int contextSize, int maxContextSize, int gpuLayers, int threads, String promptTemplate, long ctime) { +import java.math.BigDecimal; + +record Model(String path, int contextSize, int maxContextSize, int gpuLayers, int threads, Double temperature, Double topP, + Integer topK, Double minP, String promptTemplate, long ctime) { + + static final double DEFAULT_TEMPERATURE = 0.8d; + + static final double DEFAULT_TOP_P = 0.95d; + + static final int DEFAULT_TOP_K = 40; + + static final double DEFAULT_MIN_P = 0.05d; + + static final double MIN_TEMPERATURE = 0d; + + static final double MAX_TEMPERATURE = 2d; + + static final double MIN_TOP_P = 0d; + + static final double MAX_TOP_P = 1d; + + static final int MIN_TOP_K = 0; + + static final int MAX_TOP_K = 500; + + static final double MIN_MIN_P = 0d; + + static final double MAX_MIN_P = 1d; + + public Model { + maxContextSize = Math.max(Math.max(1, maxContextSize), Math.max(1, contextSize)); + contextSize = clampInt(contextSize, 1, maxContextSize); + gpuLayers = Math.max(0, gpuLayers); + threads = Math.max(0, threads); + temperature = sanitizeDouble(temperature, DEFAULT_TEMPERATURE, MIN_TEMPERATURE, MAX_TEMPERATURE); + topP = sanitizeDouble(topP, DEFAULT_TOP_P, MIN_TOP_P, MAX_TOP_P); + topK = sanitizeInt(topK, DEFAULT_TOP_K, MIN_TOP_K, MAX_TOP_K); + minP = sanitizeDouble(minP, DEFAULT_MIN_P, MIN_MIN_P, MAX_MIN_P); + promptTemplate = promptTemplate == null ? "" : promptTemplate; + } + + double temperatureValue() { + return this.temperature; + } + + double topPValue() { + return this.topP; + } + + int topKValue() { + return this.topK; + } + + double minPValue() { + return this.minP; + } + + String temperatureDisplay() { + return formatDecimal(this.temperatureValue()); + } + + String topPDisplay() { + return formatDecimal(this.topPValue()); + } + + String topKDisplay() { + return String.valueOf(this.topKValue()); + } + + String minPDisplay() { + return formatDecimal(this.minPValue()); + } + + private static Double sanitizeDouble(final Double value, final double defaultValue, final double minValue, final double maxValue) { + if (value == null || value.isNaN() || value.isInfinite()) { + return defaultValue; + } + return Math.max(minValue, Math.min(maxValue, value)); + } + + private static Integer sanitizeInt(final Integer value, final int defaultValue, final int minValue, final int maxValue) { + if (value == null) { + return defaultValue; + } + return clampInt(value, minValue, maxValue); + } + + private static int clampInt(final int value, final int minValue, final int maxValue) { + return Math.max(minValue, Math.min(maxValue, value)); + } + + static String formatDecimal(final double value) { + return BigDecimal.valueOf(value).stripTrailingZeros().toPlainString(); + } } diff --git a/src/de/extio/lm_launcher/ModelPropertiesDialog.java b/src/de/extio/lm_launcher/ModelPropertiesDialog.java index e37b945..e573958 100644 --- a/src/de/extio/lm_launcher/ModelPropertiesDialog.java +++ b/src/de/extio/lm_launcher/ModelPropertiesDialog.java @@ -12,6 +12,7 @@ import java.util.function.Consumer; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.WindowConstants; @@ -42,18 +43,27 @@ public class ModelPropertiesDialog extends JDialog { JTextField textField_3; JLabel lblPromptTemplate; JTextField txtPrompttemplate; + JLabel lblTemperature; + JTextField txtTemperature; + JLabel lblTopP; + JTextField txtTopP; + JLabel lblTopK; + JTextField txtTopK; + JLabel lblMinP; + JTextField txtMinP; /** * Create the dialog. */ public ModelPropertiesDialog(final Frame parent, final String modelPath, final Model editModel, final Consumer modelConsumer) { super(parent); + final Model modelToEdit = editModel != null ? editModel : Data.defaultModel(modelPath); this.setTitle("Model properties"); this.setModalityType(ModalityType.APPLICATION_MODAL); this.setModal(true); this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - this.setBounds(UIScaler.scale(100), UIScaler.scale(100), UIScaler.scale(678), UIScaler.scale(191)); + this.setBounds(UIScaler.scale(100), UIScaler.scale(100), UIScaler.scale(678), UIScaler.scale(320)); this.getContentPane().setLayout(new BorderLayout()); this.contentPanel.setBorder(new EmptyBorder(UIScaler.scale(5), UIScaler.scale(5), UIScaler.scale(5), UIScaler.scale(5))); this.getContentPane().add(this.contentPanel, BorderLayout.CENTER); @@ -106,6 +116,46 @@ public class ModelPropertiesDialog extends JDialog { this.contentPanel.add(this.textField_1); this.textField_1.setColumns(10); } + { + this.lblTemperature = new JLabel("Temperature"); + this.contentPanel.add(this.lblTemperature); + } + { + this.txtTemperature = new JTextField(); + this.txtTemperature.setText(Model.formatDecimal(Model.DEFAULT_TEMPERATURE)); + this.contentPanel.add(this.txtTemperature); + this.txtTemperature.setColumns(10); + } + { + this.lblTopP = new JLabel("Top P"); + this.contentPanel.add(this.lblTopP); + } + { + this.txtTopP = new JTextField(); + this.txtTopP.setText(Model.formatDecimal(Model.DEFAULT_TOP_P)); + this.contentPanel.add(this.txtTopP); + this.txtTopP.setColumns(10); + } + { + this.lblTopK = new JLabel("Top K"); + this.contentPanel.add(this.lblTopK); + } + { + this.txtTopK = new JTextField(); + this.txtTopK.setText(String.valueOf(Model.DEFAULT_TOP_K)); + this.contentPanel.add(this.txtTopK); + this.txtTopK.setColumns(10); + } + { + this.lblMinP = new JLabel("Min P"); + this.contentPanel.add(this.lblMinP); + } + { + this.txtMinP = new JTextField(); + this.txtMinP.setText(Model.formatDecimal(Model.DEFAULT_MIN_P)); + this.contentPanel.add(this.txtMinP); + this.txtMinP.setColumns(10); + } { final JPanel buttonPane = new JPanel(); buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); @@ -116,17 +166,30 @@ public class ModelPropertiesDialog extends JDialog { @Override public void actionPerformed(final ActionEvent e) { - ModelPropertiesDialog.this.setVisible(false); - final Model modelToSave = editModel != null ? editModel : Data.defaultModel(modelPath); - modelConsumer.accept( - new Model(modelPath, - Integer.parseInt(ModelPropertiesDialog.this.textField_3.getText()), - Integer.parseInt(ModelPropertiesDialog.this.textField.getText()), - Integer.parseInt(ModelPropertiesDialog.this.textField_1.getText()), - Integer.parseInt(ModelPropertiesDialog.this.textField_2.getText()), - ModelPropertiesDialog.this.txtPrompttemplate.getText(), - modelToSave.ctime())); - ModelPropertiesDialog.this.dispose(); + try { + final int contextSize = ModelPropertiesDialog.clampInt(ModelPropertiesDialog.parseIntField(ModelPropertiesDialog.this.textField_3, "Context Length"), 1, + Integer.MAX_VALUE); + final int maxContextSize = ModelPropertiesDialog.clampInt(ModelPropertiesDialog.parseIntField(ModelPropertiesDialog.this.textField, "Max Context Length"), 1, + Integer.MAX_VALUE); + final int gpuLayers = Math.max(0, ModelPropertiesDialog.parseIntField(ModelPropertiesDialog.this.textField_1, "GPU Layers")); + final int threads = Math.max(0, ModelPropertiesDialog.parseIntField(ModelPropertiesDialog.this.textField_2, "Threads")); + final double temperature = ModelPropertiesDialog.clampDouble(ModelPropertiesDialog.parseDoubleField(ModelPropertiesDialog.this.txtTemperature, "Temperature"), + Model.MIN_TEMPERATURE, Model.MAX_TEMPERATURE); + final double topP = ModelPropertiesDialog.clampDouble(ModelPropertiesDialog.parseDoubleField(ModelPropertiesDialog.this.txtTopP, "Top P"), Model.MIN_TOP_P, + Model.MAX_TOP_P); + final int topK = ModelPropertiesDialog.clampInt(ModelPropertiesDialog.parseIntField(ModelPropertiesDialog.this.txtTopK, "Top K"), Model.MIN_TOP_K, + Model.MAX_TOP_K); + final double minP = ModelPropertiesDialog.clampDouble(ModelPropertiesDialog.parseDoubleField(ModelPropertiesDialog.this.txtMinP, "Min P"), Model.MIN_MIN_P, + Model.MAX_MIN_P); + ModelPropertiesDialog.this.setVisible(false); + modelConsumer.accept( + new Model(modelPath, contextSize, maxContextSize, gpuLayers, threads, temperature, topP, topK, minP, ModelPropertiesDialog.this.txtPrompttemplate.getText(), + modelToEdit.ctime())); + ModelPropertiesDialog.this.dispose(); + } + catch (final NumberFormatException nfe) { + JOptionPane.showMessageDialog(ModelPropertiesDialog.this, nfe.getMessage(), "Invalid model property", JOptionPane.WARNING_MESSAGE); + } } }); okButton.setActionCommand("OK"); @@ -145,16 +208,44 @@ public class ModelPropertiesDialog extends JDialog { contentPanel.add(this.txtPrompttemplate); this.txtPrompttemplate.setColumns(10); } - if (editModel != null) { - this.textField.setText(String.valueOf(editModel.maxContextSize())); - this.textField_1.setText(String.valueOf(editModel.gpuLayers())); - this.textField_2.setText(String.valueOf(editModel.threads())); - this.textField_3.setText(String.valueOf(editModel.contextSize())); - this.txtPrompttemplate.setText(editModel.promptTemplate()); - } + this.textField.setText(String.valueOf(modelToEdit.maxContextSize())); + this.textField_1.setText(String.valueOf(modelToEdit.gpuLayers())); + this.textField_2.setText(String.valueOf(modelToEdit.threads())); + this.textField_3.setText(String.valueOf(modelToEdit.contextSize())); + this.txtTemperature.setText(modelToEdit.temperatureDisplay()); + this.txtTopP.setText(modelToEdit.topPDisplay()); + this.txtTopK.setText(modelToEdit.topKDisplay()); + this.txtMinP.setText(modelToEdit.minPDisplay()); + this.txtPrompttemplate.setText(modelToEdit.promptTemplate()); // Apply font scaling to all components UIScaler.scaleComponentFonts(this); } + private static int parseIntField(final JTextField field, final String fieldName) { + try { + return Integer.parseInt(field.getText().trim()); + } + catch (final NumberFormatException nfe) { + throw new NumberFormatException(fieldName + " must be a whole number."); + } + } + + private static double parseDoubleField(final JTextField field, final String fieldName) { + try { + return Double.parseDouble(field.getText().trim()); + } + catch (final NumberFormatException nfe) { + throw new NumberFormatException(fieldName + " must be a decimal number."); + } + } + + private static int clampInt(final int value, final int minValue, final int maxValue) { + return Math.max(minValue, Math.min(maxValue, value)); + } + + private static double clampDouble(final double value, final double minValue, final double maxValue) { + return Math.max(minValue, Math.min(maxValue, value)); + } + } \ No newline at end of file