Compare commits

...

5 Commits

Author SHA1 Message Date
Blake Mizerany
39a199bb3e remove duplicate check for ".." 2024-04-24 15:06:19 -07:00
Blake Mizerany
1b21a22d0e types/model: require all names parts start with an alnum char 2024-04-24 11:54:49 -07:00
Blake Mizerany
ade4b55520 types/model: make ParseName use default without question (#3886) 2024-04-24 11:52:55 -07:00
Daniel Hiltgen
a6d62e0617 Merge pull request #3882 from dhiltgen/amd_gfx
AMD gfx patch rev is hex
2024-04-24 11:07:49 -07:00
Daniel Hiltgen
0d6687f84c AMD gfx patch rev is hex
Correctly handle gfx90a discovery
2024-04-24 09:43:52 -07:00
4 changed files with 79 additions and 66 deletions

View File

@@ -140,7 +140,7 @@ func AMDGetGPUInfo() []GpuInfo {
}
if int(major) < RocmComputeMin {
slog.Warn(fmt.Sprintf("amdgpu too old gfx%d%d%d", major, minor, patch), "gpu", gpuID)
slog.Warn(fmt.Sprintf("amdgpu too old gfx%d%d%x", major, minor, patch), "gpu", gpuID)
continue
}
@@ -266,7 +266,7 @@ func AMDGetGPUInfo() []GpuInfo {
}
slog.Debug("rocm supported GPUs", "types", supported)
}
gfx := fmt.Sprintf("gfx%d%d%d", gpuInfo.Major, gpuInfo.Minor, gpuInfo.Patch)
gfx := fmt.Sprintf("gfx%d%d%x", gpuInfo.Major, gpuInfo.Minor, gpuInfo.Patch)
if !slices.Contains[[]string, string](supported, gfx) {
slog.Warn("amdgpu is not supported", "gpu", gpuInfo.ID, "gpu_type", gfx, "library", libDir, "supported_types", supported)
// TODO - consider discrete markdown just for ROCM troubleshooting?

View File

@@ -149,13 +149,16 @@ func AMDGetGPUInfo() []GpuInfo {
}
}
if patch != "" {
gpuInfo.Patch, err = strconv.Atoi(patch)
// Patch rev is hex; e.g. gfx90a
p, err := strconv.ParseInt(patch, 16, 0)
if err != nil {
slog.Info("failed to parse version", "version", gfx, "error", err)
} else {
gpuInfo.Patch = int(p)
}
}
if gpuInfo.Major < RocmComputeMin {
slog.Warn(fmt.Sprintf("amdgpu [%s] too old gfx%d%d%d", gpuInfo.ID, gpuInfo.Major, gpuInfo.Minor, gpuInfo.Patch))
slog.Warn(fmt.Sprintf("amdgpu [%s] too old gfx%d%d%x", gpuInfo.ID, gpuInfo.Major, gpuInfo.Minor, gpuInfo.Patch))
continue
}

View File

@@ -150,7 +150,7 @@ type Name struct {
// For any valid s, the fill string is used to fill in missing parts of the
// Name. The fill string must be a valid Name with the exception that any part
// may be the string ("?"), which will not be considered for filling.
func ParseName(s, fill string) Name {
func ParseNameFill(s, fill string) Name {
var r Name
parts(s)(func(kind PartKind, part string) bool {
if kind == PartDigest && !ParseDigest(part).IsValid() {
@@ -170,6 +170,13 @@ func ParseName(s, fill string) Name {
return Name{}
}
// ParseName parses s into a Name, and returns the result of filling it
// with FillDefault. The input string must be a valid string representation
// of a model
func ParseName(s string) Name {
return ParseNameFill(s, "")
}
func parseMask(s string) Name {
var r Name
parts(s)(func(kind PartKind, part string) bool {
@@ -187,7 +194,7 @@ func parseMask(s string) Name {
}
func MustParseName(s, fill string) Name {
r := ParseName(s, fill)
r := ParseNameFill(s, fill)
if !r.IsValid() {
panic("invalid Name: " + s)
}
@@ -198,7 +205,7 @@ func MustParseName(s, fill string) Name {
//
// The returned Name will only be valid if dst is valid.
//
// It skipps fill parts that are "?".
// It skips fill parts that are "?".
func fillName(r Name, fill string) Name {
fill = cmp.Or(fill, FillDefault)
f := parseMask(fill)
@@ -472,7 +479,6 @@ func parts(s string) iter_Seq2[PartKind, string] {
return
}
numConsecutiveDots := 0
partLen := 0
state, j := PartDigest, len(s)
for i := len(s) - 1; i >= 0; i-- {
@@ -544,15 +550,6 @@ func parts(s string) iter_Seq2[PartKind, string] {
yield(PartExtraneous, s[i+1:j])
return
}
default:
if s[i] == '.' {
if numConsecutiveDots++; numConsecutiveDots > 1 {
yield(state, "")
return
}
} else {
numConsecutiveDots = 0
}
}
}
@@ -579,7 +576,11 @@ func (r Name) IsValid() bool {
// it trims any leading "/" and then calls [ParseName] with fill.
func ParseNameFromURLPath(s, fill string) Name {
s = strings.TrimPrefix(s, "/")
return ParseName(s, fill)
return ParseNameFill(s, fill)
}
func ParseNameFromURLPathFill(s, fill string) Name {
return ParseNameFill(s, fill)
}
// URLPath returns a complete, canonicalized, relative URL path using the parts of a
@@ -675,7 +676,10 @@ func IsValidNamePart(kind PartKind, s string) bool {
return false
}
var consecutiveDots int
for _, c := range []byte(s) {
for i, c := range []byte(s) {
if i == 0 && !isAlphaNumeric(c) {
return false
}
if c == '.' {
if consecutiveDots++; consecutiveDots >= 2 {
return false
@@ -690,6 +694,10 @@ func IsValidNamePart(kind PartKind, s string) bool {
return true
}
func isAlphaNumeric(c byte) bool {
return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
}
func isValidByteFor(kind PartKind, c byte) bool {
if kind == PartNamespace && c == '.' {
return false

View File

@@ -101,6 +101,8 @@ var testNames = map[string]fields{
"./../passwd": {},
"./0+..": {},
"-h": {},
strings.Repeat("a", MaxNamePartLen): {model: strings.Repeat("a", MaxNamePartLen)},
strings.Repeat("a", MaxNamePartLen+1): {},
}
@@ -117,13 +119,13 @@ func TestIsValidNameLen(t *testing.T) {
// preventing path traversal.
func TestNameConsecutiveDots(t *testing.T) {
for i := 1; i < 10; i++ {
s := strings.Repeat(".", i)
s := "a" + strings.Repeat(".", i)
if i > 1 {
if g := ParseName(s, FillNothing).DisplayLong(); g != "" {
if g := ParseNameFill(s, FillNothing).DisplayLong(); g != "" {
t.Errorf("ParseName(%q) = %q; want empty string", s, g)
}
} else {
if g := ParseName(s, FillNothing).DisplayLong(); g != s {
if g := ParseNameFill(s, FillNothing).DisplayLong(); g != s {
t.Errorf("ParseName(%q) = %q; want %q", s, g, s)
}
}
@@ -156,14 +158,14 @@ func TestParseName(t *testing.T) {
s := prefix + baseName
t.Run(s, func(t *testing.T) {
name := ParseName(s, FillNothing)
name := ParseNameFill(s, FillNothing)
got := fieldsFromName(name)
if got != want {
t.Errorf("ParseName(%q) = %q; want %q", s, got, want)
}
// test round-trip
if !ParseName(name.DisplayLong(), FillNothing).EqualFold(name) {
if !ParseNameFill(name.DisplayLong(), FillNothing).EqualFold(name) {
t.Errorf("ParseName(%q).String() = %s; want %s", s, name.DisplayLong(), baseName)
}
})
@@ -188,7 +190,7 @@ func TestParseNameFill(t *testing.T) {
for _, tt := range cases {
t.Run(tt.in, func(t *testing.T) {
name := ParseName(tt.in, tt.fill)
name := ParseNameFill(tt.in, tt.fill)
if g := name.DisplayLong(); g != tt.want {
t.Errorf("ParseName(%q, %q) = %q; want %q", tt.in, tt.fill, g, tt.want)
}
@@ -201,7 +203,7 @@ func TestParseNameFill(t *testing.T) {
t.Fatal("expected panic")
}
}()
ParseName("x", "^")
ParseNameFill("x", "^")
})
}
@@ -212,7 +214,7 @@ func TestParseNameHTTPDoublePrefixStrip(t *testing.T) {
}
for _, s := range cases {
t.Run(s, func(t *testing.T) {
name := ParseName(s, FillNothing)
name := ParseNameFill(s, FillNothing)
if name.IsValid() {
t.Errorf("expected invalid path; got %#v", name)
}
@@ -237,7 +239,7 @@ func TestCompleteWithAndWithoutBuild(t *testing.T) {
for _, tt := range cases {
t.Run(tt.in, func(t *testing.T) {
p := ParseName(tt.in, FillNothing)
p := ParseNameFill(tt.in, FillNothing)
t.Logf("ParseName(%q) = %#v", tt.in, p)
if g := p.IsComplete(); g != tt.complete {
t.Errorf("Complete(%q) = %v; want %v", tt.in, g, tt.complete)
@@ -252,7 +254,7 @@ func TestCompleteWithAndWithoutBuild(t *testing.T) {
// inlined when used in Complete, preventing any allocations or
// escaping to the heap.
allocs := testing.AllocsPerRun(1000, func() {
keep(ParseName("complete.com/x/mistral:latest+Q4_0", FillNothing).IsComplete())
keep(ParseNameFill("complete.com/x/mistral:latest+Q4_0", FillNothing).IsComplete())
})
if allocs > 0 {
t.Errorf("Complete allocs = %v; want 0", allocs)
@@ -269,7 +271,7 @@ func TestNameLogValue(t *testing.T) {
t.Run(s, func(t *testing.T) {
var b bytes.Buffer
log := slog.New(slog.NewTextHandler(&b, nil))
name := ParseName(s, FillNothing)
name := ParseNameFill(s, FillNothing)
log.Info("", "name", name)
want := fmt.Sprintf("name=%s", name.GoString())
got := b.String()
@@ -316,7 +318,7 @@ func TestNameGoString(t *testing.T) {
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
p := ParseName(tt.in, FillNothing)
p := ParseNameFill(tt.in, FillNothing)
tt.wantGoString = cmp.Or(tt.wantGoString, tt.in)
if g := fmt.Sprintf("%#v", p); g != tt.wantGoString {
t.Errorf("GoString() = %q; want %q", g, tt.wantGoString)
@@ -326,7 +328,7 @@ func TestNameGoString(t *testing.T) {
}
func TestDisplayLongest(t *testing.T) {
g := ParseName("example.com/library/mistral:latest+Q4_0", FillNothing).DisplayLongest()
g := ParseNameFill("example.com/library/mistral:latest+Q4_0", FillNothing).DisplayLongest()
if g != "example.com/library/mistral:latest" {
t.Errorf("got = %q; want %q", g, "example.com/library/mistral:latest")
}
@@ -339,17 +341,17 @@ func TestDisplayShortest(t *testing.T) {
want string
wantPanic bool
}{
{"example.com/library/mistral:latest+Q4_0", "example.com/library/_:latest", "mistral", false},
{"example.com/library/mistral:latest+Q4_0", "example.com/_/_:latest", "library/mistral", false},
{"example.com/library/mistral:latest+Q4_0", "example.com/library/?:latest", "mistral", false},
{"example.com/library/mistral:latest+Q4_0", "example.com/?/?:latest", "library/mistral", false},
{"example.com/library/mistral:latest+Q4_0", "", "example.com/library/mistral", false},
{"example.com/library/mistral:latest+Q4_0", "", "example.com/library/mistral", false},
// case-insensitive
{"Example.com/library/mistral:latest+Q4_0", "example.com/library/_:latest", "mistral", false},
{"example.com/Library/mistral:latest+Q4_0", "example.com/library/_:latest", "mistral", false},
{"example.com/library/Mistral:latest+Q4_0", "example.com/library/_:latest", "Mistral", false},
{"example.com/library/mistral:Latest+Q4_0", "example.com/library/_:latest", "mistral", false},
{"example.com/library/mistral:Latest+q4_0", "example.com/library/_:latest", "mistral", false},
{"Example.com/library/mistral:latest+Q4_0", "example.com/library/?:latest", "mistral", false},
{"example.com/Library/mistral:latest+Q4_0", "example.com/library/?:latest", "mistral", false},
{"example.com/library/Mistral:latest+Q4_0", "example.com/library/?:latest", "Mistral", false},
{"example.com/library/mistral:Latest+Q4_0", "example.com/library/?:latest", "mistral", false},
{"example.com/library/mistral:Latest+q4_0", "example.com/library/?:latest", "mistral", false},
// zero value
{"", MaskDefault, "", true},
@@ -361,10 +363,10 @@ func TestDisplayShortest(t *testing.T) {
{"registry.ollama.ai/library/mistral:latest+Q4_0", MaskDefault, "mistral", false},
// Auto-Fill
{"x", "example.com/library/_:latest", "x", false},
{"x", "example.com/library/_:latest+Q4_0", "x", false},
{"x/y:z", "a.com/library/_:latest+Q4_0", "x/y:z", false},
{"x/y:z", "a.com/library/_:latest+Q4_0", "x/y:z", false},
{"x", "example.com/library/?:latest", "x", false},
{"x", "example.com/library/?:latest+Q4_0", "x", false},
{"x/y:z", "a.com/library/?:latest+Q4_0", "x/y:z", false},
{"x/y:z", "a.com/library/?:latest+Q4_0", "x/y:z", false},
}
for _, tt := range cases {
@@ -377,7 +379,7 @@ func TestDisplayShortest(t *testing.T) {
}
}()
p := ParseName(tt.in, FillNothing)
p := ParseNameFill(tt.in, FillNothing)
t.Logf("ParseName(%q) = %#v", tt.in, p)
if g := p.DisplayShortest(tt.mask); g != tt.want {
t.Errorf("got = %q; want %q", g, tt.want)
@@ -388,7 +390,7 @@ func TestDisplayShortest(t *testing.T) {
func TestParseNameAllocs(t *testing.T) {
allocs := testing.AllocsPerRun(1000, func() {
keep(ParseName("example.com/mistral:7b+Q4_0", FillNothing))
keep(ParseNameFill("example.com/mistral:7b+Q4_0", FillNothing))
})
if allocs > 0 {
t.Errorf("ParseName allocs = %v; want 0", allocs)
@@ -399,7 +401,7 @@ func BenchmarkParseName(b *testing.B) {
b.ReportAllocs()
for range b.N {
keep(ParseName("example.com/mistral:7b+Q4_0", FillNothing))
keep(ParseNameFill("example.com/mistral:7b+Q4_0", FillNothing))
}
}
@@ -430,7 +432,7 @@ func FuzzParseName(f *testing.F) {
f.Add(":@!@")
f.Add("...")
f.Fuzz(func(t *testing.T, s string) {
r0 := ParseName(s, FillNothing)
r0 := ParseNameFill(s, FillNothing)
if strings.Contains(s, "..") && !r0.IsZero() {
t.Fatalf("non-zero value for path with '..': %q", s)
@@ -453,7 +455,7 @@ func FuzzParseName(f *testing.F) {
t.Errorf("String() did not round-trip with case insensitivity: %q\ngot = %q\nwant = %q", s, r0.DisplayLong(), s)
}
r1 := ParseName(r0.DisplayLong(), FillNothing)
r1 := ParseNameFill(r0.DisplayLong(), FillNothing)
if !r0.EqualFold(r1) {
t.Errorf("round-trip mismatch: %+v != %+v", r0, r1)
}
@@ -461,7 +463,7 @@ func FuzzParseName(f *testing.F) {
}
func TestNameStringAllocs(t *testing.T) {
name := ParseName("example.com/ns/mistral:latest+Q4_0", FillNothing)
name := ParseNameFill("example.com/ns/mistral:latest+Q4_0", FillNothing)
allocs := testing.AllocsPerRun(1000, func() {
keep(name.DisplayLong())
})
@@ -483,7 +485,7 @@ func TestNamePath(t *testing.T) {
}
for _, tt := range cases {
t.Run(tt.in, func(t *testing.T) {
p := ParseName(tt.in, FillNothing)
p := ParseNameFill(tt.in, FillNothing)
t.Logf("ParseName(%q) = %#v", tt.in, p)
if g := p.DisplayURLPath(); g != tt.want {
t.Errorf("got = %q; want %q", g, tt.want)
@@ -526,7 +528,7 @@ func TestNameFilepath(t *testing.T) {
}
for _, tt := range cases {
t.Run(tt.in, func(t *testing.T) {
p := ParseName(tt.in, FillNothing)
p := ParseNameFill(tt.in, FillNothing)
t.Logf("ParseName(%q) = %#v", tt.in, p)
g := p.Filepath()
g = filepath.ToSlash(g)
@@ -587,7 +589,7 @@ func TestParseNameFilepath(t *testing.T) {
t.Run(tt.in, func(t *testing.T) {
in := strings.ReplaceAll(tt.in, "/", string(filepath.Separator))
fill := cmp.Or(tt.fill, FillNothing)
want := ParseName(tt.want, fill)
want := ParseNameFill(tt.want, fill)
if g := ParseNameFromFilepath(in, fill); !g.EqualFold(want) {
t.Errorf("got = %q; want %q", g.DisplayLong(), tt.want)
}
@@ -645,12 +647,12 @@ func ExampleName_MapHash() {
m := map[uint64]bool{}
// key 1
m[ParseName("mistral:latest+q4", FillNothing).MapHash()] = true
m[ParseName("miSTRal:latest+Q4", FillNothing).MapHash()] = true
m[ParseName("mistral:LATest+Q4", FillNothing).MapHash()] = true
m[ParseNameFill("mistral:latest+q4", FillNothing).MapHash()] = true
m[ParseNameFill("miSTRal:latest+Q4", FillNothing).MapHash()] = true
m[ParseNameFill("mistral:LATest+Q4", FillNothing).MapHash()] = true
// key 2
m[ParseName("mistral:LATest", FillNothing).MapHash()] = true
m[ParseNameFill("mistral:LATest", FillNothing).MapHash()] = true
fmt.Println(len(m))
// Output:
@@ -659,9 +661,9 @@ func ExampleName_MapHash() {
func ExampleName_CompareFold_sort() {
names := []Name{
ParseName("mistral:latest", FillNothing),
ParseName("mistRal:7b+q4", FillNothing),
ParseName("MIstral:7b", FillNothing),
ParseNameFill("mistral:latest", FillNothing),
ParseNameFill("mistRal:7b+q4", FillNothing),
ParseNameFill("MIstral:7b", FillNothing),
}
slices.SortFunc(names, Name.CompareFold)
@@ -682,7 +684,7 @@ func ExampleName_completeAndResolved() {
"x/y/z:latest+q4_0",
"@sha123-abc",
} {
name := ParseName(s, FillNothing)
name := ParseNameFill(s, FillNothing)
fmt.Printf("complete:%v resolved:%v digest:%s\n", name.IsComplete(), name.IsResolved(), name.Digest())
}
@@ -693,15 +695,15 @@ func ExampleName_completeAndResolved() {
}
func ExampleName_DisplayShortest() {
name := ParseName("example.com/jmorganca/mistral:latest+Q4_0", FillNothing)
name := ParseNameFill("example.com/jmorganca/mistral:latest+Q4_0", FillNothing)
fmt.Println(name.DisplayShortest("example.com/jmorganca/_:latest"))
fmt.Println(name.DisplayShortest("example.com/_/_:latest"))
fmt.Println(name.DisplayShortest("example.com/_/_:_"))
fmt.Println(name.DisplayShortest("_/_/_:_"))
fmt.Println(name.DisplayShortest("example.com/jmorganca/?:latest"))
fmt.Println(name.DisplayShortest("example.com/?/?:latest"))
fmt.Println(name.DisplayShortest("example.com/?/?:?"))
fmt.Println(name.DisplayShortest("?/?/?:?"))
// Default
name = ParseName("registry.ollama.ai/library/mistral:latest+Q4_0", FillNothing)
name = ParseNameFill("registry.ollama.ai/library/mistral:latest+Q4_0", FillNothing)
fmt.Println(name.DisplayShortest(""))
// Output: