mirror of
https://github.com/anchore/syft.git
synced 2026-11-05 17:06:32 +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>
57 lines
2.0 KiB
Go
57 lines
2.0 KiB
Go
package ai
|
|
|
|
import (
|
|
"github.com/anchore/syft/syft/artifact"
|
|
"github.com/anchore/syft/syft/pkg"
|
|
)
|
|
|
|
// ggufMergeProcessor consolidates multiple GGUF packages into a single package
|
|
// representing the AI model. When scanning OCI images with multiple layers,
|
|
// each layer may produce a separate package. This processor finds the package
|
|
// with a name and merges metadata from nameless packages into its Parts field.
|
|
// Only packages with a non-empty name are returned in the final result.
|
|
func ggufMergeProcessor(pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
|
|
// don't early-return on err: it aggregates per-file failures, and skipping the
|
|
// merge would drop every nameless layer and the named package's Parts along
|
|
// with it (see safeTensorsMergeProcessor).
|
|
if len(pkgs) == 0 {
|
|
return pkgs, rels, err
|
|
}
|
|
|
|
// separate packages with names from those without
|
|
var namedPkgs []pkg.Package
|
|
var namelessHeaders []pkg.GGUFFileHeader
|
|
|
|
for _, p := range pkgs {
|
|
if p.Name != "" {
|
|
namedPkgs = append(namedPkgs, p)
|
|
} else {
|
|
if header, ok := p.Metadata.(pkg.GGUFFileHeader); ok {
|
|
// we do not want a kv hash for nameless headers
|
|
header.MetadataKeyValuesHash = ""
|
|
namelessHeaders = append(namelessHeaders, header)
|
|
}
|
|
}
|
|
}
|
|
|
|
// if there are no named packages, return nothing
|
|
if len(namedPkgs) == 0 {
|
|
return nil, rels, err
|
|
}
|
|
|
|
// merge nameless headers into a single named package;
|
|
// if there are multiple named packages, return them without trying to merge headers.
|
|
// we cannot determine which nameless headers belong to which package
|
|
// this is because the order we receive the gguf headers in is not guaranteed
|
|
// to match the layer order in the original oci image
|
|
if len(namedPkgs) == 1 && len(namelessHeaders) > 0 {
|
|
winner := &namedPkgs[0]
|
|
if header, ok := winner.Metadata.(pkg.GGUFFileHeader); ok {
|
|
header.Parts = namelessHeaders
|
|
winner.Metadata = header
|
|
}
|
|
}
|
|
|
|
return namedPkgs, rels, err
|
|
}
|