mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 07:26:34 +01:00
* fix(ai): keep merging GGUF layers when one fails to parse a single unparseable layer in an OCI GGUF artifact skipped the merge, so the named model lost its parts and nameless layers were dropped. The error is still returned. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(ai): only send GGUF layers to the GGUF cataloger the media type glob `application/vnd.docker.ai*` also matched config, model.file, license and safetensors layers, so every safetensors OCI model reported spurious "invalid GGUF magic number" unknowns. Match `application/vnd.docker.ai.gguf.v3` exactly. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(ai): don't wrap safetensors parameter counts - tensors whose shape product overflows uint64 are skipped instead of wrapping - per-file and per-shard parameter totals saturate instead of wrapping - parameter count and dominant dtype are computed in one pass - GGUF licenses are created with the scan context, and drop no-op unknown checks Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * refactor(ai): derive safetensors model info from the header in one place the file and OCI layer parsers built identical metadata from a parsed header. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix(ai): keep the GGUF media type with the GGUF cataloger also corrects stale comments in the GGUF merge processor. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
82 lines
2.3 KiB
Go
82 lines
2.3 KiB
Go
package ai
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/anchore/syft/syft/pkg"
|
|
)
|
|
|
|
func Test_ggufMergeProcessor(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pkgs []pkg.Package
|
|
wantPkgCount int
|
|
wantFilePartCount int
|
|
}{
|
|
{
|
|
name: "single named package merges nameless headers",
|
|
pkgs: []pkg.Package{
|
|
{Name: "model", Metadata: pkg.GGUFFileHeader{MetadataKeyValuesHash: "abc"}},
|
|
{Name: "", Metadata: pkg.GGUFFileHeader{MetadataKeyValuesHash: "part1"}},
|
|
{Name: "", Metadata: pkg.GGUFFileHeader{MetadataKeyValuesHash: "part2"}},
|
|
},
|
|
wantPkgCount: 1,
|
|
wantFilePartCount: 2,
|
|
},
|
|
{
|
|
name: "multiple named packages returns all without merging",
|
|
pkgs: []pkg.Package{
|
|
{Name: "model1", Metadata: pkg.GGUFFileHeader{}},
|
|
{Name: "model2", Metadata: pkg.GGUFFileHeader{}},
|
|
{Name: "", Metadata: pkg.GGUFFileHeader{}},
|
|
},
|
|
wantPkgCount: 2,
|
|
wantFilePartCount: 0,
|
|
},
|
|
{
|
|
name: "no named packages returns empty result",
|
|
pkgs: []pkg.Package{
|
|
{Name: "", Metadata: pkg.GGUFFileHeader{}},
|
|
{Name: "", Metadata: pkg.GGUFFileHeader{}},
|
|
},
|
|
wantPkgCount: 0,
|
|
wantFilePartCount: 0,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
got, _, err := ggufMergeProcessor(test.pkgs, nil, nil)
|
|
require.NoError(t, err)
|
|
assert.Len(t, got, test.wantPkgCount)
|
|
|
|
if test.wantPkgCount == 1 && test.wantFilePartCount > 0 {
|
|
header, ok := got[0].Metadata.(pkg.GGUFFileHeader)
|
|
require.True(t, ok)
|
|
assert.Len(t, header.Parts, test.wantFilePartCount)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_ggufMergeProcessor_mergesDespiteError(t *testing.T) {
|
|
// one layer failing to parse must not drop the rest of the model
|
|
parseErr := errors.New("one layer failed to parse")
|
|
pkgs := []pkg.Package{
|
|
{Name: "model", Metadata: pkg.GGUFFileHeader{MetadataKeyValuesHash: "abc"}},
|
|
{Name: "", Metadata: pkg.GGUFFileHeader{MetadataKeyValuesHash: "part1"}},
|
|
}
|
|
|
|
got, _, err := ggufMergeProcessor(pkgs, nil, parseErr)
|
|
require.ErrorIs(t, err, parseErr)
|
|
require.Len(t, got, 1)
|
|
assert.Equal(t, "model", got[0].Name)
|
|
header, ok := got[0].Metadata.(pkg.GGUFFileHeader)
|
|
require.True(t, ok)
|
|
assert.Len(t, header.Parts, 1)
|
|
}
|