Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions cmd/cli/desktop/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func (c *Client) Status() Status {
func (c *Client) Pull(model string, ignoreRuntimeMemoryCheck bool, printer standalone.StatusPrinter) (string, bool, error) {
model = normalizeHuggingFaceModelName(model)

return c.withRetries("download", 3, printer, func(attempt int) (string, bool, error, bool) {
message, shown, err := c.withRetries("download", 3, printer, func(attempt int) (string, bool, error, bool) {
jsonData, err := json.Marshal(dmrm.ModelCreateRequest{From: model, IgnoreRuntimeMemoryCheck: ignoreRuntimeMemoryCheck})
if err != nil {
// Marshaling errors are not retryable
Expand Down Expand Up @@ -148,6 +148,13 @@ func (c *Client) Pull(model string, ignoreRuntimeMemoryCheck bool, printer stand

return message, shown, nil, false
})

// Enhance error message with helpful context if there was an error
if err != nil {
err = enhanceErrorMessage(err, model)
}

return message, shown, err
}

// isRetryableError determines if an error is retryable (network-related)
Expand All @@ -164,9 +171,27 @@ func isRetryableError(err error) bool {
return true
}

// Fall back to string matching for network errors that don't have specific types
// This is necessary because many network errors are only available as strings
errStr := err.Error()
// Check error message patterns
errStrLower := strings.ToLower(err.Error())

// Errors that should never be retried
nonRetryablePatterns := []string{
"sharded gguf", // HuggingFace doesn't support sharded GGUF in OCI format
"manifest unknown", // Model doesn't exist
"name unknown", // Repository doesn't exist
"unauthorized", // Authentication error
"forbidden", // Permission denied
"not found", // Resource doesn't exist
"invalid reference", // Malformed reference
}

for _, pattern := range nonRetryablePatterns {
if strings.Contains(errStrLower, pattern) {
return false
}
}

// Check for network errors that are retryable
retryablePatterns := []string{
"connection refused",
"connection reset",
Expand All @@ -180,14 +205,40 @@ func isRetryableError(err error) bool {
}

for _, pattern := range retryablePatterns {
if strings.Contains(strings.ToLower(errStr), pattern) {
if strings.Contains(errStrLower, pattern) {
return true
}
}

return false
}

// enhanceErrorMessage provides more helpful error messages for known error patterns
func enhanceErrorMessage(err error, model string) error {
if err == nil {
return nil
}

errStr := err.Error()
errStrLower := strings.ToLower(errStr)

// Provide helpful context for sharded GGUF errors
if strings.Contains(errStrLower, "sharded gguf") {
return fmt.Errorf("%w\n\nNote: Sharded GGUF models from HuggingFace are not currently supported due to OCI registry limitations.\n"+
"Workaround: Upload the model to Docker Hub or use a non-sharded quantization.\n"+
"For more information, see: https://github.com/ollama/ollama/issues/5245", err)
}

// Provide helpful context for manifest/repository not found errors
if strings.Contains(errStrLower, "manifest unknown") || strings.Contains(errStrLower, "name unknown") {
return fmt.Errorf("%w\n\nNote: Model or quantization tag not found. "+
"For HuggingFace models, ensure the quantization tag (e.g., :Q4_K_M) exists.\n"+
"Available quantizations are typically listed in the model's Files tab on HuggingFace.", err)
}

return err
}

// withRetries executes an operation with automatic retry logic for transient failures
func (c *Client) withRetries(
operationName string,
Expand Down
59 changes: 59 additions & 0 deletions cmd/cli/desktop/desktop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,14 @@ func TestIsRetryableError(t *testing.T) {
{"generic non-retryable error", errors.New("a generic non-retryable error"), false},
{"service unavailable error", ErrServiceUnavailable, true},
{"deadline exceeded", context.DeadlineExceeded, true},
// Non-retryable errors
{"sharded gguf error", errors.New("contains sharded GGUF message"), false},
{"manifest unknown error", errors.New("manifest unknown"), false},
{"name unknown error", errors.New("name unknown"), false},
{"unauthorized error", errors.New("unauthorized access"), false},
{"forbidden error", errors.New("forbidden resource"), false},
{"not found error", errors.New("not found"), false},
{"invalid reference error", errors.New("invalid reference format"), false},
}

for _, tt := range tests {
Expand All @@ -398,3 +406,54 @@ func TestIsRetryableError(t *testing.T) {
}
}

func TestEnhanceErrorMessage(t *testing.T) {
tests := []struct {
name string
err error
model string
expectedSubstr string
}{
{
name: "sharded gguf error gets enhanced",
err: errors.New("repository contains sharded GGUF"),
model: "hf.co/unsloth/model:UD-Q4_K_XL",
expectedSubstr: "Sharded GGUF models from HuggingFace are not currently supported",
},
{
name: "manifest unknown error gets enhanced",
err: errors.New("manifest unknown"),
model: "hf.co/bartowski/model:Q4_K_S",
expectedSubstr: "Model or quantization tag not found",
},
{
name: "name unknown error gets enhanced",
err: errors.New("name unknown"),
model: "hf.co/nonexistent/model",
expectedSubstr: "Model or quantization tag not found",
},
{
name: "generic error not enhanced",
err: errors.New("connection refused"),
model: "test/model",
expectedSubstr: "connection refused",
},
{
name: "nil error returns nil",
err: nil,
model: "test/model",
expectedSubstr: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := enhanceErrorMessage(tt.err, tt.model)
if tt.err == nil {
assert.Nil(t, result)
} else {
assert.NotNil(t, result)
assert.Contains(t, result.Error(), tt.expectedSubstr)
}
})
}
}
11 changes: 11 additions & 0 deletions cmd/cli/docs/reference/model_pull.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,14 @@ To specify the quantization, provide it as a tag, for example:
```console
docker model pull hf.co/bartowski/Llama-3.2-1B-Instruct-GGUF
```

#### Known Limitations

**Sharded GGUF models:** Some models on HuggingFace use sharded GGUF format (where the model is split across multiple files).
These models cannot currently be pulled directly from HuggingFace due to OCI registry limitations.
If you encounter an error about "sharded GGUF", you have two options:

1. Use a non-sharded quantization of the same model if available
2. Upload the model to Docker Hub or another OCI-compliant registry and pull from there

For more information, see: https://github.com/ollama/ollama/issues/5245
17 changes: 0 additions & 17 deletions pkg/go-containerregistry/cmd/krane/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ replace github.com/google/go-containerregistry => ../../
require (
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.11.0
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589
github.com/google/go-containerregistry v0.20.3
)

require (
cloud.google.com/go/compute/metadata v0.7.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.30 // indirect
Expand All @@ -36,26 +34,11 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect
github.com/aws/smithy-go v1.23.2 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/cli v28.2.2+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.4 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
gotest.tools/v3 v3.1.0 // indirect
)
54 changes: 0 additions & 54 deletions pkg/go-containerregistry/cmd/krane/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading