-
Notifications
You must be signed in to change notification settings - Fork 174
Enable loading of lora-adapter parts in dev mode #1098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
25e9f06
2f94a5e
350a0ad
e43a990
79a426d
87f4adf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,6 +85,28 @@ func runDev(ctx context.Context, options *DevStartOptions) error { | |
| return err | ||
| } | ||
|
|
||
| // Detect and collect lora-adapter parts | ||
| var loraPaths []string | ||
| for _, part := range kitfile.Model.Parts { | ||
| if strings.EqualFold(part.Type, "lora-adapter") { | ||
| if part.Path == "" { | ||
| continue | ||
| } | ||
| partAbsPath, _, err := filesystem.VerifySubpath(options.contextDir, part.Path) | ||
| if err != nil { | ||
| output.Debugf("Skipping lora-adapter: %v", err) | ||
| continue | ||
| } | ||
| loraPath, err := findLoraAdapterFile(partAbsPath) | ||
| if err != nil { | ||
| output.Debugf("Skipping lora-adapter: %v", err) | ||
| continue | ||
| } | ||
| output.Infof("Found lora-adapter: %s", loraPath) | ||
| loraPaths = append(loraPaths, loraPath) | ||
| } | ||
| } | ||
|
|
||
| llmHarness := &harness.LLMHarness{} | ||
| llmHarness.Host = options.host | ||
| llmHarness.Port = options.port | ||
|
|
@@ -93,7 +115,7 @@ func runDev(ctx context.Context, options *DevStartOptions) error { | |
| return err | ||
| } | ||
|
|
||
| if err := llmHarness.Start(modelPath); err != nil { | ||
| if err := llmHarness.Start(modelPath, loraPaths); err != nil { | ||
| return err | ||
| } | ||
|
|
||
|
|
@@ -170,6 +192,50 @@ func findModelFile(absPath string) (string, error) { | |
| return modelPath, nil | ||
| } | ||
|
|
||
| // findLoraAdapterFile validates a lora adapter path. | ||
| // The path must point to a regular file. | ||
| func findLoraAdapterFile(absPath string) (string, error) { | ||
| stat, err := os.Lstat(absPath) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| // Case 1: direct file | ||
| if stat.Mode().IsRegular() { | ||
| if !strings.HasSuffix(strings.ToLower(absPath), ".gguf") { | ||
| return "", fmt.Errorf("lora adapter file must be a .gguf file: %s", absPath) | ||
| } | ||
| output.Debugf("Found lora adapter path at %s", absPath) | ||
| return absPath, nil | ||
| } | ||
|
|
||
| // Case 2: directory → search for .gguf | ||
| if stat.IsDir() { | ||
| entries, err := os.ReadDir(absPath) | ||
| if err != nil { | ||
| return "", fmt.Errorf("error searching for lora adapter in %s: %w", absPath, err) | ||
| } | ||
|
|
||
| var found string | ||
| for _, entry := range entries { | ||
| if entry.Type().IsRegular() && strings.HasSuffix(strings.ToLower(entry.Name()), ".gguf") { | ||
| path := filepath.Join(absPath, entry.Name()) | ||
| if found != "" { | ||
| return "", fmt.Errorf("multiple lora adapter files found: %s and %s", found, path) | ||
| } | ||
| found = path | ||
| } | ||
| } | ||
| if found == "" { | ||
| return "", fmt.Errorf("no .gguf lora adapter found in %s", absPath) | ||
| } | ||
| output.Debugf("Found lora adapter path in directory %s at %s", absPath, found) | ||
| return found, nil | ||
| } | ||
|
Comment on lines
+212
to
+234
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know me and @gorkem are giving conflicting review here, but I'm still of the opinion that if you have a model part with type |
||
|
|
||
| return "", fmt.Errorf("lora adapter path %s is not a regular file or directory", absPath) | ||
| } | ||
|
|
||
| // extractModelKitToCache extracts a ModelKit reference to a cache directory | ||
| // using the unpack library with model filter | ||
| func extractModelKitToCache(ctx context.Context, options *DevStartOptions) error { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -63,7 +63,7 @@ func (harness *LLMHarness) Init() error { | |
| return nil | ||
| } | ||
|
|
||
| func (harness *LLMHarness) Start(modelPath string) (err error) { | ||
| func (harness *LLMHarness) Start(modelPath string, loraPaths []string) (err error) { | ||
|
|
||
| harnessPath := constants.HarnessPath(harness.ConfigHome) | ||
| pidFile := filepath.Join(harnessPath, constants.HarnessProcessFile) | ||
|
|
@@ -85,10 +85,13 @@ func (harness *LLMHarness) Start(modelPath string) (err error) { | |
|
|
||
| uiHome := filepath.Join(harnessPath, "ui") | ||
| output.Debugf("model path is %s", modelPath) | ||
| for _, loraPath := range loraPaths { | ||
| output.Debugf("lora adapter path is %s", loraPath) | ||
| } | ||
| var cmd *exec.Cmd | ||
|
|
||
| if runtime.GOOS == "windows" { | ||
| cmd = exec.Command( | ||
| "./llamafile.exe", | ||
| args := []string{ | ||
| "--server", | ||
| "--model", modelPath, | ||
| "--host", harness.Host, | ||
|
|
@@ -97,11 +100,20 @@ func (harness *LLMHarness) Start(modelPath string) (err error) { | |
| "--gpu", "AUTO", | ||
| "--nobrowser", | ||
| "--unsecure", | ||
| ) | ||
| } | ||
| for _, loraPath := range loraPaths { | ||
| args = append(args, "--lora", loraPath) | ||
| } | ||
| cmd = exec.Command("./llamafile.exe", args...) | ||
| } else { | ||
| // Build command string for sh -c (required for APE binaries on Linux) | ||
| loraArgs := "" | ||
| for _, loraPath := range loraPaths { | ||
| loraArgs += fmt.Sprintf(" --lora %s", loraPath) | ||
| } | ||
| cmd = exec.Command("sh", "-c", | ||
| fmt.Sprintf("./llamafile --server --model %s --host %s --port %d --path %s --gpu AUTO --nobrowser --unsecure", | ||
| modelPath, harness.Host, harness.Port, uiHome), | ||
| fmt.Sprintf("./llamafile --server --model %s --host %s --port %d --path %s --gpu AUTO --nobrowser --unsecure%s", | ||
| modelPath, harness.Host, harness.Port, uiHome, loraArgs), | ||
|
Comment on lines
114
to
+116
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand reverting to using args := []string{
"--server",
"--model", modelPath,
"--host", harness.Host,
"--port", fmt.Sprintf("%d", harness.Port),
"--path", uiHome,
"--gpu", "AUTO",
"--nobrowser",
"--unsecure",
}
for _, loraPath := range loraPaths {
args = append(args, "--lora", loraPath)
}
if runtime.GOOS == "windows" {
cmd = exec.Command("./llamafile.exe", args...)
} else {
// Note: running llamafile with sh -c is required as it is an APE binary (is this still true?)
llamaCmd := "./llamafile " + strings.Join(args, " ")
cmd = exec.Command("sh", "-c", llamaCmd)
} |
||
| ) | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.