diff --git a/commands/package.go b/commands/package.go new file mode 100644 index 00000000..985f4d84 --- /dev/null +++ b/commands/package.go @@ -0,0 +1,145 @@ +package commands + +import ( + "bufio" + "encoding/json" + "fmt" + "html" + "io" + "path/filepath" + + "github.com/docker/model-cli/desktop" + "github.com/docker/model-distribution/builder" + "github.com/docker/model-distribution/registry" + "github.com/spf13/cobra" +) + +func newPackagedCmd() *cobra.Command { + var opts packageOptions + + c := &cobra.Command{ + Use: "package --gguf [--license ...] --push TARGET", + Short: "package a model", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf( + "'docker model package' requires 1 argument.\n\n"+ + "Usage: %s\n\n"+ + "See 'docker model package --help' for more information", + cmd.Use, + ) + } + if opts.push != true { + return fmt.Errorf( + "This version of 'docker model package' requires --push and will write the resulting package directly to the registry.\n\n" + + "See 'docker model package --help' for more information", + ) + } + if opts.ggufPath == "" { + return fmt.Errorf( + "GGUF path is required.\n\n" + + "See 'docker model package --help' for more information", + ) + } + if !filepath.IsAbs(opts.ggufPath) { + return fmt.Errorf( + "GGUF path must be absolute.\n\n" + + "See 'docker model package --help' for more information", + ) + } + opts.ggufPath = filepath.Clean(opts.ggufPath) + + for i, l := range opts.licensePaths { + if !filepath.IsAbs(l) { + return fmt.Errorf( + "license path must be absolute.\n\n" + + "See 'docker model package --help' for more information", + ) + } + opts.licensePaths[i] = filepath.Clean(l) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := packageModel(cmd, args[0], opts); err != nil { + cmd.PrintErrln("Failed to package model") + return fmt.Errorf("package model: %w", err) + } + return nil + }, + } + + c.Flags().StringVar(&opts.ggufPath, "gguf", "", "absolute path to gguf file (required)") + c.Flags().StringArrayVarP(&opts.licensePaths, "license", "l", nil, "absolute path to a license file") + c.Flags().BoolVar(&opts.push, "push", false, "push to registry (required)") + return c +} + +type packageOptions struct { + ggufPath string + licensePaths []string + push bool +} + +func packageModel(cmd *cobra.Command, tag string, opts packageOptions) error { + // Parse the reference + cmd.PrintErrln("Packaging model %q\n", tag) + target, err := registry.NewClient( + registry.WithUserAgent("docker-model-cli/" + desktop.Version), + ).NewTarget(tag) + if err != nil { + return err + } + + // Create package builder with GGUF file + cmd.PrintErrf("Adding GGUF file from %q\n", opts.ggufPath) + pkg, err := builder.FromGGUF(opts.ggufPath) + if err != nil { + return fmt.Errorf("add gguf file: %w", err) + } + + // Add license files + for _, path := range opts.licensePaths { + cmd.PrintErrf("Adding license file from %q\n", path) + pkg, err = pkg.WithLicense(path) + if err != nil { + return fmt.Errorf("add license file: %w", err) + } + } + + // Write the artifact to the registry + cmd.PrintErrln("Pushing to registry...") + pr, pw := io.Pipe() + done := make(chan error, 1) + go func() { + defer pw.Close() + done <- pkg.Build(cmd.Context(), target, pw) + }() + + scanner := bufio.NewScanner(pr) + for scanner.Scan() { + progressLine := scanner.Text() + if progressLine == "" { + continue + } + + // Parse the progress message + var progressMsg desktop.ProgressMessage + if err := json.Unmarshal([]byte(html.UnescapeString(progressLine)), &progressMsg); err != nil { + cmd.PrintErrln("Error displaying progress:", err) + } + + // Print progress messages + TUIProgress(progressMsg.Message) + } + cmd.PrintErrln("") // newline after progress + + if err := scanner.Err(); err != nil { + cmd.PrintErrln("Error streaming progress:", err) + } + if err := <-done; err != nil { + return fmt.Errorf("push: %w", err) + } + cmd.PrintErrln("Model pushed successfully") + return nil +} diff --git a/commands/root.go b/commands/root.go index 6fbe2f30..f1257e01 100644 --- a/commands/root.go +++ b/commands/root.go @@ -74,6 +74,7 @@ func NewRootCmd(cli *command.DockerCli) *cobra.Command { newStatusCmd(), newPullCmd(), newPushCmd(), + newPackagedCmd(), newListCmd(), newLogsCmd(), newRunCmd(), diff --git a/go.mod b/go.mod index a27198e0..a02eaae9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,8 @@ require ( github.com/docker/cli v28.0.1+incompatible github.com/docker/docker v28.0.1+incompatible github.com/docker/go-units v0.5.0 - github.com/docker/model-runner v0.0.0-20250430163045-4c3ffbfa5311 + github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57 + github.com/docker/model-runner v0.0.0-20250512190413-96af7b750f88 github.com/google/go-containerregistry v0.20.3 github.com/nxadm/tail v1.4.8 github.com/olekukonko/tablewriter v0.0.5 @@ -37,7 +38,6 @@ require ( github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/model-distribution v0.0.0-20250423075433-587f475e591d // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect @@ -47,9 +47,12 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/gpustack/gguf-parser-go v0.14.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect + github.com/henvic/httpretty v0.1.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/gorm v1.9.16 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect @@ -59,6 +62,8 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/term v0.5.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -69,7 +74,9 @@ require ( github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/smallnest/ringbuffer v0.0.0-20241116012123-461381446e3d // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a // indirect github.com/vbatts/tar-split v0.11.6 // indirect @@ -84,11 +91,15 @@ require ( go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect golang.org/x/crypto v0.35.0 // indirect + golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect + golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.36.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/term v0.29.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.29.0 // indirect + gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect google.golang.org/grpc v1.70.0 // indirect diff --git a/go.sum b/go.sum index a8f53058..268c305f 100644 --- a/go.sum +++ b/go.sum @@ -68,10 +68,10 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/model-distribution v0.0.0-20250423075433-587f475e591d h1:lyxdxjNHTSyQ2w1rjbuu5pbgX42AD3kmxWLNv3mdqQ4= -github.com/docker/model-distribution v0.0.0-20250423075433-587f475e591d/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c= -github.com/docker/model-runner v0.0.0-20250430163045-4c3ffbfa5311 h1:VAN98Xol6PH2NnHe4uNxiX//7XU+uUrfr2SmjyyoKq0= -github.com/docker/model-runner v0.0.0-20250430163045-4c3ffbfa5311/go.mod h1:9x6YLoYMHOsX1ppytOCvluEtZiFxnEc9zGHfTLs3pmQ= +github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57 h1:ZqfKknb+0/uJid8XLFwSl/osjE+WuS6o6I3dh3ZqO4U= +github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c= +github.com/docker/model-runner v0.0.0-20250512190413-96af7b750f88 h1:NkiizYL67HsCnnlEU6BQVoeiC1bAAyJFxw02bO7JC4E= +github.com/docker/model-runner v0.0.0-20250512190413-96af7b750f88/go.mod h1:Nw+rx6RRPNdProEb9/BVJyAQn63px6WWlOv+eEpkV7Q= github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= diff --git a/vendor/github.com/docker/model-distribution/builder/builder.go b/vendor/github.com/docker/model-distribution/builder/builder.go new file mode 100644 index 00000000..cfb84839 --- /dev/null +++ b/vendor/github.com/docker/model-distribution/builder/builder.go @@ -0,0 +1,49 @@ +package builder + +import ( + "context" + "fmt" + "io" + + "github.com/docker/model-distribution/internal/gguf" + "github.com/docker/model-distribution/internal/mutate" + "github.com/docker/model-distribution/internal/partial" + "github.com/docker/model-distribution/types" +) + +// Builder builds a model artifact +type Builder struct { + model types.ModelArtifact +} + +// FromGGUF returns a *Builder that builds a model artifacts from a GGUF file +func FromGGUF(path string) (*Builder, error) { + mdl, err := gguf.NewModel(path) + if err != nil { + return nil, err + } + return &Builder{ + model: mdl, + }, nil +} + +// WithLicense adds a license file to the artifact +func (b *Builder) WithLicense(path string) (*Builder, error) { + licenseLayer, err := partial.NewLayer(path, types.MediaTypeLicense) + if err != nil { + return nil, fmt.Errorf("license layer from %q: %w", path, err) + } + return &Builder{ + model: mutate.AppendLayers(b.model, licenseLayer), + }, nil +} + +// Target represents a build target +type Target interface { + Write(context.Context, types.ModelArtifact, io.Writer) error +} + +// Build finalizes the artifact and writes it to the given target, reporting progress to the given writer +func (b *Builder) Build(ctx context.Context, target Target, pw io.Writer) error { + return target.Write(ctx, b.model, pw) +} diff --git a/vendor/github.com/docker/model-distribution/distribution/client.go b/vendor/github.com/docker/model-distribution/distribution/client.go index 4d2502b7..fe5ef970 100644 --- a/vendor/github.com/docker/model-distribution/distribution/client.go +++ b/vendor/github.com/docker/model-distribution/distribution/client.go @@ -6,27 +6,20 @@ import ( "io" "net/http" "os" - "strings" - "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/sirupsen/logrus" + "github.com/docker/model-distribution/internal/progress" "github.com/docker/model-distribution/internal/store" + "github.com/docker/model-distribution/registry" "github.com/docker/model-distribution/types" ) -const ( - defaultUserAgent = "model-distribution" -) - // Client provides model distribution functionality type Client struct { - store *store.LocalStore - log *logrus.Entry - remoteOptions []remote.Option + store *store.LocalStore + log *logrus.Entry + registry *registry.Client } // GetStorePath returns the root path where models are stored @@ -84,8 +77,8 @@ func WithUserAgent(ua string) Option { func defaultOptions() *options { return &options{ logger: logrus.NewEntry(logrus.StandardLogger()), - transport: remote.DefaultTransport, - userAgent: defaultUserAgent, + transport: registry.DefaultTransport, + userAgent: registry.DefaultUserAgent, } } @@ -111,11 +104,10 @@ func NewClient(opts ...Option) (*Client, error) { return &Client{ store: s, log: options.logger, - remoteOptions: []remote.Option{ - remote.WithAuthFromKeychain(authn.DefaultKeychain), - remote.WithTransport(options.transport), - remote.WithUserAgent(options.userAgent), - }, + registry: registry.NewClient( + registry.WithTransport(options.transport), + registry.WithUserAgent(options.userAgent), + ), }, nil } @@ -123,38 +115,18 @@ func NewClient(opts ...Option) (*Client, error) { func (c *Client) PullModel(ctx context.Context, reference string, progressWriter io.Writer) error { c.log.Infoln("Starting model pull:", reference) - // Parse the reference - ref, err := name.ParseReference(reference) - if err != nil { - return NewReferenceError(reference, err) - } - - // First, check the remote registry for the model's digest - c.log.Infoln("Checking remote registry for model:", reference) - opts := append([]remote.Option{remote.WithContext(ctx)}, c.remoteOptions...) - remoteImg, err := remote.Image(ref, opts...) + remoteModel, err := c.registry.Model(ctx, reference) if err != nil { - errStr := err.Error() - if strings.Contains(errStr, "UNAUTHORIZED") { - return NewPullError(reference, "UNAUTHORIZED", "Authentication required for this model", err) - } - if strings.Contains(errStr, "MANIFEST_UNKNOWN") { - return NewPullError(reference, "MANIFEST_UNKNOWN", "Model not found", err) - } - if strings.Contains(errStr, "NAME_UNKNOWN") { - return NewPullError(reference, "NAME_UNKNOWN", "Repository not found", err) - } - c.log.Errorln("Failed to check remote image:", err, "reference:", reference) - return NewPullError(reference, "UNKNOWN", err.Error(), err) + return fmt.Errorf("reading model from registry: %w", err) } //Check for supported type - if err := checkCompat(remoteImg); err != nil { + if err := checkCompat(remoteModel); err != nil { return err } // Get the remote image digest - remoteDigest, err := remoteImg.Digest() + remoteDigest, err := remoteModel.Digest() if err != nil { c.log.Errorln("Failed to get remote image digest:", err) return fmt.Errorf("getting remote image digest: %w", err) @@ -178,7 +150,7 @@ func (c *Client) PullModel(ctx context.Context, reference string, progressWriter // Report progress for local model size := fileInfo.Size() - err = writeSuccess(progressWriter, fmt.Sprintf("Using cached model: %.2f MB", float64(size)/1024/1024)) + err = progress.WriteSuccess(progressWriter, fmt.Sprintf("Using cached model: %.2f MB", float64(size)/1024/1024)) if err != nil { c.log.Warnf("Writing progress: %v", err) // If we fail to write progress, don't try again @@ -196,15 +168,15 @@ func (c *Client) PullModel(ctx context.Context, reference string, progressWriter // Model doesn't exist in local store or digests don't match, pull from remote - pr := newProgressReporter(progressWriter, pullMsg) + pr := progress.NewProgressReporter(progressWriter, progress.PullMsg) defer func() { if err := pr.Wait(); err != nil { c.log.Warnf("Failed to write progress: %v", err) } }() - if err = c.store.Write(remoteImg, []string{reference}, pr.updates()); err != nil { - if writeErr := writeError(progressWriter, fmt.Sprintf("Error: %s", err.Error())); writeErr != nil { + if err = c.store.Write(remoteModel, []string{reference}, pr.Updates()); err != nil { + if writeErr := progress.WriteError(progressWriter, fmt.Sprintf("Error: %s", err.Error())); writeErr != nil { c.log.Warnf("Failed to write error message: %v", writeErr) // If we fail to write error message, don't try again progressWriter = nil @@ -212,7 +184,7 @@ func (c *Client) PullModel(ctx context.Context, reference string, progressWriter return fmt.Errorf("writing image to store: %w", err) } - if err := writeSuccess(progressWriter, "Model pulled successfully"); err != nil { + if err := progress.WriteSuccess(progressWriter, "Model pulled successfully"); err != nil { c.log.Warnf("Failed to write success message: %v", err) // If we fail to write success message, don't try again progressWriter = nil @@ -307,9 +279,9 @@ func (c *Client) Tag(source string, target string) error { // PushModel pushes a tagged model from the content store to the registry. func (c *Client) PushModel(ctx context.Context, tag string, progressWriter io.Writer) (err error) { // Parse the tag - ref, err := name.NewTag(tag) + target, err := c.registry.NewTarget(tag) if err != nil { - return fmt.Errorf("invalid tag %q: %w", tag, err) + return fmt.Errorf("new tag: %w", err) } // Get the model from the store @@ -320,36 +292,23 @@ func (c *Client) PushModel(ctx context.Context, tag string, progressWriter io.Wr // Push the model c.log.Infoln("Pushing model:", tag) - - pr := newProgressReporter(progressWriter, pushMsg) - defer func() { - if err := pr.Wait(); err != nil { - c.log.Warnf("Failed to write progress: %v", err) - } - }() - - opts := append([]remote.Option{ - remote.WithContext(ctx), - remote.WithProgress(pr.updates()), - }, c.remoteOptions...) - - if err := remote.Write(ref, mdl, opts...); err != nil { + if err := target.Write(ctx, mdl, progressWriter); err != nil { c.log.Errorln("Failed to push image:", err, "reference:", tag) - if writeErr := writeError(progressWriter, fmt.Sprintf("Error: %s", err.Error())); writeErr != nil { + if writeErr := progress.WriteError(progressWriter, fmt.Sprintf("Error: %s", err.Error())); writeErr != nil { c.log.Warnf("Failed to write error message: %v", writeErr) } return fmt.Errorf("pushing image: %w", err) } c.log.Infoln("Successfully pushed model:", tag) - if err := writeSuccess(progressWriter, "Model pushed successfully"); err != nil { + if err := progress.WriteSuccess(progressWriter, "Model pushed successfully"); err != nil { c.log.Warnf("Failed to write success message: %v", err) } return nil } -func checkCompat(image v1.Image) error { +func checkCompat(image types.ModelArtifact) error { manifest, err := image.Manifest() if err != nil { return err diff --git a/vendor/github.com/docker/model-distribution/distribution/errors.go b/vendor/github.com/docker/model-distribution/distribution/errors.go index d6149117..4a02e722 100644 --- a/vendor/github.com/docker/model-distribution/distribution/errors.go +++ b/vendor/github.com/docker/model-distribution/distribution/errors.go @@ -5,13 +5,13 @@ import ( "fmt" "github.com/docker/model-distribution/internal/store" + "github.com/docker/model-distribution/registry" "github.com/docker/model-distribution/types" ) var ( - ErrInvalidReference = errors.New("invalid model reference") - ErrModelNotFound = store.ErrModelNotFound - ErrUnauthorized = errors.New("unauthorized access to model") + ErrInvalidReference = registry.ErrInvalidReference + ErrModelNotFound = store.ErrModelNotFound // model not found in store ErrUnsupportedMediaType = errors.New(fmt.Sprintf( "client supports only models of type %q and older - try upgrading", types.MediaTypeModelConfigV01, @@ -37,51 +37,3 @@ func (e *ReferenceError) Unwrap() error { func (e *ReferenceError) Is(target error) bool { return target == ErrInvalidReference } - -// PullError represents an error that occurs when pulling a model -type PullError struct { - Reference string - // Code should be one of error codes defined in the distribution spec - // (see https://github.com/opencontainers/distribution-spec/blob/583e014d15418d839d67f68152bc2c83821770e0/spec.md#error-codes) - Code string - Message string - Err error -} - -func (e *PullError) Error() string { - return fmt.Sprintf("failed to pull model %q: %s - %s", e.Reference, e.Code, e.Message) -} - -func (e *PullError) Unwrap() error { - return e.Err -} - -// Is implements error matching for PullError -func (e *PullError) Is(target error) bool { - switch target { - case ErrModelNotFound: - return e.Code == "MANIFEST_UNKNOWN" || e.Code == "NAME_UNKNOWN" - case ErrUnauthorized: - return e.Code == "UNAUTHORIZED" - default: - return false - } -} - -// NewReferenceError creates a new ReferenceError -func NewReferenceError(reference string, err error) error { - return &ReferenceError{ - Reference: reference, - Err: err, - } -} - -// NewPullError creates a new PullError -func NewPullError(reference, code, message string, err error) error { - return &PullError{ - Reference: reference, - Code: code, - Message: message, - Err: err, - } -} diff --git a/vendor/github.com/docker/model-distribution/internal/gguf/create.go b/vendor/github.com/docker/model-distribution/internal/gguf/create.go new file mode 100644 index 00000000..c83ed923 --- /dev/null +++ b/vendor/github.com/docker/model-distribution/internal/gguf/create.go @@ -0,0 +1,56 @@ +package gguf + +import ( + "fmt" + "strings" + "time" + + v1 "github.com/google/go-containerregistry/pkg/v1" + parser "github.com/gpustack/gguf-parser-go" + + "github.com/docker/model-distribution/internal/partial" + "github.com/docker/model-distribution/types" +) + +func NewModel(path string) (*Model, error) { + layer, err := partial.NewLayer(path, types.MediaTypeGGUF) + if err != nil { + return nil, fmt.Errorf("create gguf layer: %w", err) + } + diffID, err := layer.DiffID() + if err != nil { + return nil, fmt.Errorf("get gguf layer diffID: %w", err) + } + + created := time.Now() + return &Model{ + configFile: types.ConfigFile{ + Config: configFromFile(path), + Descriptor: types.Descriptor{ + Created: &created, + }, + RootFS: v1.RootFS{ + Type: "rootfs", + DiffIDs: []v1.Hash{ + diffID, + }, + }, + }, + layers: []v1.Layer{layer}, + }, nil +} + +func configFromFile(path string) types.Config { + gguf, err := parser.ParseGGUFFile(path) + if err != nil { + return types.Config{} // continue without metadata + } + return types.Config{ + Format: types.FormatGGUF, + Parameters: strings.TrimSpace(gguf.Metadata().Parameters.String()), + Architecture: strings.TrimSpace(gguf.Metadata().Architecture), + Quantization: strings.TrimSpace(gguf.Metadata().FileType.String()), + Size: strings.TrimSpace(gguf.Metadata().Size.String()), + GGUF: extractGGUFMetadata(&gguf.Header), + } +} diff --git a/vendor/github.com/docker/model-distribution/internal/gguf/metadata.go b/vendor/github.com/docker/model-distribution/internal/gguf/metadata.go new file mode 100644 index 00000000..c373dcc2 --- /dev/null +++ b/vendor/github.com/docker/model-distribution/internal/gguf/metadata.go @@ -0,0 +1,95 @@ +package gguf + +import ( + "fmt" + "strings" + + parser "github.com/gpustack/gguf-parser-go" +) + +const maxArraySize = 50 + +// extractGGUFMetadata converts the GGUF header metadata into a string map. +func extractGGUFMetadata(header *parser.GGUFHeader) map[string]string { + metadata := make(map[string]string) + + for _, kv := range header.MetadataKV { + if kv.ValueType == parser.GGUFMetadataValueTypeArray { + arrayValue := kv.ValueArray() + if arrayValue.Len > maxArraySize { + continue + } + } + var value string + switch kv.ValueType { + case parser.GGUFMetadataValueTypeUint8: + value = fmt.Sprintf("%d", kv.ValueUint8()) + case parser.GGUFMetadataValueTypeInt8: + value = fmt.Sprintf("%d", kv.ValueInt8()) + case parser.GGUFMetadataValueTypeUint16: + value = fmt.Sprintf("%d", kv.ValueUint16()) + case parser.GGUFMetadataValueTypeInt16: + value = fmt.Sprintf("%d", kv.ValueInt16()) + case parser.GGUFMetadataValueTypeUint32: + value = fmt.Sprintf("%d", kv.ValueUint32()) + case parser.GGUFMetadataValueTypeInt32: + value = fmt.Sprintf("%d", kv.ValueInt32()) + case parser.GGUFMetadataValueTypeUint64: + value = fmt.Sprintf("%d", kv.ValueUint64()) + case parser.GGUFMetadataValueTypeInt64: + value = fmt.Sprintf("%d", kv.ValueInt64()) + case parser.GGUFMetadataValueTypeFloat32: + value = fmt.Sprintf("%f", kv.ValueFloat32()) + case parser.GGUFMetadataValueTypeFloat64: + value = fmt.Sprintf("%f", kv.ValueFloat64()) + case parser.GGUFMetadataValueTypeBool: + value = fmt.Sprintf("%t", kv.ValueBool()) + case parser.GGUFMetadataValueTypeString: + value = kv.ValueString() + case parser.GGUFMetadataValueTypeArray: + value = handleArray(kv.ValueArray()) + default: + value = fmt.Sprintf("[unknown type %d]", kv.ValueType) + } + metadata[kv.Key] = value + } + + return metadata +} + +// handleArray processes an array value and returns its string representation +func handleArray(arrayValue parser.GGUFMetadataKVArrayValue) string { + var values []string + for _, v := range arrayValue.Array { + switch arrayValue.Type { + case parser.GGUFMetadataValueTypeUint8: + values = append(values, fmt.Sprintf("%d", v.(uint8))) + case parser.GGUFMetadataValueTypeInt8: + values = append(values, fmt.Sprintf("%d", v.(int8))) + case parser.GGUFMetadataValueTypeUint16: + values = append(values, fmt.Sprintf("%d", v.(uint16))) + case parser.GGUFMetadataValueTypeInt16: + values = append(values, fmt.Sprintf("%d", v.(int16))) + case parser.GGUFMetadataValueTypeUint32: + values = append(values, fmt.Sprintf("%d", v.(uint32))) + case parser.GGUFMetadataValueTypeInt32: + values = append(values, fmt.Sprintf("%d", v.(int32))) + case parser.GGUFMetadataValueTypeUint64: + values = append(values, fmt.Sprintf("%d", v.(uint64))) + case parser.GGUFMetadataValueTypeInt64: + values = append(values, fmt.Sprintf("%d", v.(int64))) + case parser.GGUFMetadataValueTypeFloat32: + values = append(values, fmt.Sprintf("%f", v.(float32))) + case parser.GGUFMetadataValueTypeFloat64: + values = append(values, fmt.Sprintf("%f", v.(float64))) + case parser.GGUFMetadataValueTypeBool: + values = append(values, fmt.Sprintf("%t", v.(bool))) + case parser.GGUFMetadataValueTypeString: + values = append(values, v.(string)) + default: + // Do nothing + } + } + + return strings.Join(values, ", ") +} diff --git a/vendor/github.com/docker/model-distribution/internal/gguf/model.go b/vendor/github.com/docker/model-distribution/internal/gguf/model.go new file mode 100644 index 00000000..80856d5a --- /dev/null +++ b/vendor/github.com/docker/model-distribution/internal/gguf/model.go @@ -0,0 +1,99 @@ +package gguf + +import ( + "encoding/json" + "fmt" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/partial" + ggcr "github.com/google/go-containerregistry/pkg/v1/types" + + mdpartial "github.com/docker/model-distribution/internal/partial" + "github.com/docker/model-distribution/types" +) + +var _ types.ModelArtifact = &Model{} + +type Model struct { + configFile types.ConfigFile + layers []v1.Layer + manifest *v1.Manifest +} + +func (m *Model) Layers() ([]v1.Layer, error) { + return m.layers, nil +} + +func (m *Model) Size() (int64, error) { + return partial.Size(m) +} + +func (m *Model) ConfigName() (v1.Hash, error) { + return partial.ConfigName(m) +} + +func (m *Model) ConfigFile() (*v1.ConfigFile, error) { + return nil, fmt.Errorf("invalid for model") +} + +func (m *Model) Digest() (v1.Hash, error) { + return partial.Digest(m) +} + +func (m *Model) Manifest() (*v1.Manifest, error) { + return mdpartial.ManifestForLayers(m) +} + +func (m *Model) LayerByDigest(hash v1.Hash) (v1.Layer, error) { + for _, l := range m.layers { + d, err := l.Digest() + if err != nil { + return nil, fmt.Errorf("get layer digest: %w", err) + } + if d == hash { + return l, nil + } + } + return nil, fmt.Errorf("layer not found") +} + +func (m *Model) LayerByDiffID(hash v1.Hash) (v1.Layer, error) { + for _, l := range m.layers { + d, err := l.DiffID() + if err != nil { + return nil, fmt.Errorf("get layer digest: %w", err) + } + if d == hash { + return l, nil + } + } + return nil, fmt.Errorf("layer not found") +} + +func (m *Model) RawManifest() ([]byte, error) { + return partial.RawManifest(m) +} + +func (m *Model) RawConfigFile() ([]byte, error) { + return json.Marshal(m.configFile) +} + +func (m *Model) MediaType() (ggcr.MediaType, error) { + manifest, err := m.Manifest() + if err != nil { + return "", fmt.Errorf("compute maniest: %w", err) + } + return manifest.MediaType, nil +} + +func (m *Model) ID() (string, error) { + return mdpartial.ID(m) +} + +func (m *Model) Config() (types.Config, error) { + return mdpartial.Config(m) +} + +func (m *Model) Descriptor() (types.Descriptor, error) { + return mdpartial.Descriptor(m) +} diff --git a/vendor/github.com/docker/model-distribution/internal/mutate/model.go b/vendor/github.com/docker/model-distribution/internal/mutate/model.go new file mode 100644 index 00000000..e6482c69 --- /dev/null +++ b/vendor/github.com/docker/model-distribution/internal/mutate/model.go @@ -0,0 +1,131 @@ +package mutate + +import ( + "encoding/json" + "fmt" + + v1 "github.com/google/go-containerregistry/pkg/v1" + ggcrpartial "github.com/google/go-containerregistry/pkg/v1/partial" + ggcr "github.com/google/go-containerregistry/pkg/v1/types" + + "github.com/docker/model-distribution/internal/partial" + "github.com/docker/model-distribution/types" +) + +type model struct { + base types.ModelArtifact + appended []v1.Layer + configMediaType ggcr.MediaType +} + +func (m *model) Descriptor() (types.Descriptor, error) { + return partial.Descriptor(m.base) +} + +func (m *model) ID() (string, error) { + return partial.ID(m) +} + +func (m *model) Config() (types.Config, error) { + return partial.Config(m) +} + +func (m *model) MediaType() (ggcr.MediaType, error) { + manifest, err := m.Manifest() + if err != nil { + return "", fmt.Errorf("compute maniest: %w", err) + } + return manifest.MediaType, nil +} + +func (m *model) Size() (int64, error) { + return ggcrpartial.Size(m) +} + +func (m *model) ConfigName() (v1.Hash, error) { + return ggcrpartial.ConfigName(m) +} + +func (m *model) ConfigFile() (*v1.ConfigFile, error) { + return nil, fmt.Errorf("invalid for model") +} + +func (m *model) Digest() (v1.Hash, error) { + return ggcrpartial.Digest(m) +} + +func (m *model) RawManifest() ([]byte, error) { + return ggcrpartial.RawManifest(m) +} + +func (m *model) LayerByDigest(hash v1.Hash) (v1.Layer, error) { + ls, err := m.Layers() + if err != nil { + return nil, err + } + for _, l := range ls { + d, err := l.Digest() + if err != nil { + return nil, fmt.Errorf("get layer digest: %w", err) + } + if d == hash { + return l, nil + } + } + return nil, fmt.Errorf("layer not found") +} + +func (m *model) LayerByDiffID(hash v1.Hash) (v1.Layer, error) { + ls, err := m.Layers() + if err != nil { + return nil, err + } + for _, l := range ls { + d, err := l.Digest() + if err != nil { + return nil, fmt.Errorf("get layer digest: %w", err) + } + if d == hash { + return l, nil + } + } + return nil, fmt.Errorf("layer not found") +} + +func (m *model) Layers() ([]v1.Layer, error) { + ls, err := m.base.Layers() + if err != nil { + return nil, err + } + return append(ls, m.appended...), nil +} + +func (m *model) Manifest() (*v1.Manifest, error) { + manifest, err := partial.ManifestForLayers(m) + if err != nil { + return nil, err + } + if m.configMediaType != "" { + manifest.Config.MediaType = m.configMediaType + } + return manifest, nil +} + +func (m *model) RawConfigFile() ([]byte, error) { + cf, err := partial.ConfigFile(m.base) + if err != nil { + return nil, err + } + for _, l := range m.appended { + diffID, err := l.DiffID() + if err != nil { + return nil, err + } + cf.RootFS.DiffIDs = append(cf.RootFS.DiffIDs, diffID) + } + raw, err := json.Marshal(cf) + if err != nil { + return nil, err + } + return raw, err +} diff --git a/vendor/github.com/docker/model-distribution/internal/mutate/mutate.go b/vendor/github.com/docker/model-distribution/internal/mutate/mutate.go new file mode 100644 index 00000000..ac729213 --- /dev/null +++ b/vendor/github.com/docker/model-distribution/internal/mutate/mutate.go @@ -0,0 +1,22 @@ +package mutate + +import ( + v1 "github.com/google/go-containerregistry/pkg/v1" + ggcr "github.com/google/go-containerregistry/pkg/v1/types" + + "github.com/docker/model-distribution/types" +) + +func AppendLayers(mdl types.ModelArtifact, layers ...v1.Layer) types.ModelArtifact { + return &model{ + base: mdl, + appended: layers, + } +} + +func ConfigMediaType(mdl types.ModelArtifact, mt ggcr.MediaType) types.ModelArtifact { + return &model{ + base: mdl, + configMediaType: mt, + } +} diff --git a/vendor/github.com/docker/model-distribution/distribution/progress.go b/vendor/github.com/docker/model-distribution/internal/progress/reporter.go similarity index 78% rename from vendor/github.com/docker/model-distribution/distribution/progress.go rename to vendor/github.com/docker/model-distribution/internal/progress/reporter.go index 8f76192e..a38cc920 100644 --- a/vendor/github.com/docker/model-distribution/distribution/progress.go +++ b/vendor/github.com/docker/model-distribution/internal/progress/reporter.go @@ -1,4 +1,4 @@ -package distribution +package progress import ( "encoding/json" @@ -6,7 +6,7 @@ import ( "io" "time" - v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1" ) // ProgressMessage represents a structured message for progress reporting @@ -17,7 +17,7 @@ type ProgressMessage struct { Pulled uint64 `json:"pulled"` // Bytes transferred so far } -type reporter struct { +type Reporter struct { progress chan v1.Update done chan struct{} err error @@ -27,16 +27,16 @@ type reporter struct { type progressF func(update v1.Update) string -func pullMsg(update v1.Update) string { +func PullMsg(update v1.Update) string { return fmt.Sprintf("Downloaded: %.2f MB", float64(update.Complete)/1024/1024) } -func pushMsg(update v1.Update) string { +func PushMsg(update v1.Update) string { return fmt.Sprintf("Uploaded: %.2f MB", float64(update.Complete)/1024/1024) } -func newProgressReporter(w io.Writer, msgF progressF) *reporter { - return &reporter{ +func NewProgressReporter(w io.Writer, msgF progressF) *Reporter { + return &Reporter{ out: w, progress: make(chan v1.Update), done: make(chan struct{}), @@ -52,9 +52,9 @@ func safeUint64(n int64) uint64 { return uint64(n) } -// updates returns a channel for receiving progress updates. It is the responsibility of the caller to close -// the channel when they are done sending updates. Should only be called once per reporter instance. -func (r *reporter) updates() chan<- v1.Update { +// Updates returns a channel for receiving progress Updates. It is the responsibility of the caller to close +// the channel when they are done sending Updates. Should only be called once per Reporter instance. +func (r *Reporter) Updates() chan<- v1.Update { go func() { var lastComplete int64 var lastUpdate time.Time @@ -82,8 +82,8 @@ func (r *reporter) updates() chan<- v1.Update { return r.progress } -// Wait waits for the progress reporter to finish and returns any error encountered. -func (r *reporter) Wait() error { +// Wait waits for the progress Reporter to finish and returns any error encountered. +func (r *Reporter) Wait() error { <-r.done return r.err } @@ -111,16 +111,16 @@ func writeProgress(w io.Writer, msg string, total, pulled uint64) error { }) } -// writeSuccess writes a success message -func writeSuccess(w io.Writer, message string) error { +// WriteSuccess writes a success message +func WriteSuccess(w io.Writer, message string) error { return writeProgressMessage(w, ProgressMessage{ Type: "success", Message: message, }) } -// writeError writes an error message -func writeError(w io.Writer, message string) error { +// WriteError writes an error message +func WriteError(w io.Writer, message string) error { return writeProgressMessage(w, ProgressMessage{ Type: "error", Message: message, diff --git a/vendor/github.com/docker/model-distribution/registry/artifact.go b/vendor/github.com/docker/model-distribution/registry/artifact.go new file mode 100644 index 00000000..b8d93313 --- /dev/null +++ b/vendor/github.com/docker/model-distribution/registry/artifact.go @@ -0,0 +1,25 @@ +package registry + +import ( + "github.com/docker/model-distribution/internal/partial" + "github.com/docker/model-distribution/types" + v1 "github.com/google/go-containerregistry/pkg/v1" +) + +var _ types.ModelArtifact = &artifact{} + +type artifact struct { + v1.Image +} + +func (a *artifact) ID() (string, error) { + return partial.ID(a) +} + +func (a *artifact) Config() (types.Config, error) { + return partial.Config(a) +} + +func (a *artifact) Descriptor() (types.Descriptor, error) { + return partial.Descriptor(a) +} diff --git a/vendor/github.com/docker/model-distribution/registry/client.go b/vendor/github.com/docker/model-distribution/registry/client.go new file mode 100644 index 00000000..4b5bcb81 --- /dev/null +++ b/vendor/github.com/docker/model-distribution/registry/client.go @@ -0,0 +1,125 @@ +package registry + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + + "github.com/docker/model-distribution/internal/progress" + "github.com/docker/model-distribution/types" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/remote" +) + +const ( + DefaultUserAgent = "model-distribution" +) + +var ( + DefaultTransport = remote.DefaultTransport +) + +type Client struct { + transport http.RoundTripper + userAgent string + keychain authn.Keychain +} + +type ClientOption func(*Client) + +func WithTransport(transport http.RoundTripper) ClientOption { + return func(c *Client) { + if transport != nil { + c.transport = transport + } + } +} + +func WithUserAgent(userAgent string) ClientOption { + return func(c *Client) { + if userAgent != "" { + c.userAgent = userAgent + } + } +} + +func NewClient(opts ...ClientOption) *Client { + client := &Client{ + transport: remote.DefaultTransport, + userAgent: DefaultUserAgent, + keychain: authn.DefaultKeychain, + } + for _, opt := range opts { + opt(client) + } + return client +} + +func (c *Client) Model(ctx context.Context, reference string) (types.ModelArtifact, error) { + // Parse the reference + ref, err := name.ParseReference(reference) + if err != nil { + return nil, NewReferenceError(reference, err) + } + + // Return the artifact at the given reference + remoteImg, err := remote.Image(ref, + remote.WithContext(ctx), + remote.WithAuthFromKeychain(c.keychain), + remote.WithTransport(c.transport), + remote.WithUserAgent(c.userAgent), + ) + if err != nil { + errStr := err.Error() + if strings.Contains(errStr, "UNAUTHORIZED") { + return nil, NewRegistryError(reference, "UNAUTHORIZED", "Authentication required for this model", err) + } + if strings.Contains(errStr, "MANIFEST_UNKNOWN") { + return nil, NewRegistryError(reference, "MANIFEST_UNKNOWN", "Model not found", err) + } + if strings.Contains(errStr, "NAME_UNKNOWN") { + return nil, NewRegistryError(reference, "NAME_UNKNOWN", "Repository not found", err) + } + return nil, NewRegistryError(reference, "UNKNOWN", err.Error(), err) + } + return &artifact{remoteImg}, nil +} + +type Target struct { + reference name.Reference + transport http.RoundTripper + userAgent string + keychain authn.Keychain +} + +func (c *Client) NewTarget(tag string) (*Target, error) { + ref, err := name.NewTag(tag) + if err != nil { + return nil, fmt.Errorf("invalid tag: %q: %w", tag, err) + } + return &Target{ + reference: ref, + transport: c.transport, + userAgent: c.userAgent, + keychain: c.keychain, + }, nil +} + +func (t *Target) Write(ctx context.Context, model types.ModelArtifact, progressWriter io.Writer) error { + pr := progress.NewProgressReporter(progressWriter, progress.PushMsg) + defer pr.Wait() + + if err := remote.Write(t.reference, model, + remote.WithContext(ctx), + remote.WithAuthFromKeychain(t.keychain), + remote.WithTransport(t.transport), + remote.WithUserAgent(t.userAgent), + remote.WithProgress(pr.Updates()), + ); err != nil { + return fmt.Errorf("write to registry %q: %w", t.reference.String(), err) + } + return nil +} diff --git a/vendor/github.com/docker/model-distribution/registry/errors.go b/vendor/github.com/docker/model-distribution/registry/errors.go new file mode 100644 index 00000000..758f2232 --- /dev/null +++ b/vendor/github.com/docker/model-distribution/registry/errors.go @@ -0,0 +1,85 @@ +package registry + +import ( + "errors" + "fmt" + + "github.com/docker/model-distribution/types" +) + +var ( + ErrInvalidReference = errors.New("invalid model reference") + ErrModelNotFound = errors.New("model not found") + ErrUnauthorized = errors.New("unauthorized access to model") + ErrUnsupportedMediaType = errors.New(fmt.Sprintf( + "client supports only models of type %q and older - try upgrading", + types.MediaTypeModelConfigV01, + )) +) + +// ReferenceError represents an error related to an invalid model reference +type ReferenceError struct { + Reference string + Err error +} + +func (e *ReferenceError) Error() string { + return fmt.Sprintf("invalid model reference %q: %v", e.Reference, e.Err) +} + +func (e *ReferenceError) Unwrap() error { + return e.Err +} + +// Is implements error matching for ReferenceError +func (e *ReferenceError) Is(target error) bool { + return target == ErrInvalidReference +} + +// Error represents an error returned by an OCI registry +type Error struct { + Reference string + // Code should be one of error codes defined in the distribution spec + // (see https://github.com/opencontainers/distribution-spec/blob/583e014d15418d839d67f68152bc2c83821770e0/spec.md#error-codes) + Code string + Message string + Err error +} + +func (e Error) Error() string { + return fmt.Sprintf("failed to pull model %q: %s - %s", e.Reference, e.Code, e.Message) +} + +func (e Error) Unwrap() error { + return e.Err +} + +// Is implements error matching for Error +func (e Error) Is(target error) bool { + switch target { + case ErrModelNotFound: + return e.Code == "MANIFEST_UNKNOWN" || e.Code == "NAME_UNKNOWN" + case ErrUnauthorized: + return e.Code == "UNAUTHORIZED" + default: + return false + } +} + +// NewReferenceError creates a new ReferenceError +func NewReferenceError(reference string, err error) error { + return &ReferenceError{ + Reference: reference, + Err: err, + } +} + +// NewRegistryError creates a new Error +func NewRegistryError(reference, code, message string, err error) error { + return &Error{ + Reference: reference, + Code: code, + Message: message, + Err: err, + } +} diff --git a/vendor/github.com/docker/model-distribution/types/config.go b/vendor/github.com/docker/model-distribution/types/config.go index 3947b02c..2dfaa6d7 100644 --- a/vendor/github.com/docker/model-distribution/types/config.go +++ b/vendor/github.com/docker/model-distribution/types/config.go @@ -4,7 +4,7 @@ import ( "strings" "time" - "github.com/google/go-containerregistry/pkg/v1" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" ) @@ -38,11 +38,12 @@ type ConfigFile struct { // Config describes the model. type Config struct { - Format Format `json:"format,omitempty"` - Quantization string `json:"quantization,omitempty"` - Parameters string `json:"parameters,omitempty"` - Architecture string `json:"architecture,omitempty"` - Size string `json:"size,omitempty"` + Format Format `json:"format,omitempty"` + Quantization string `json:"quantization,omitempty"` + Parameters string `json:"parameters,omitempty"` + Architecture string `json:"architecture,omitempty"` + Size string `json:"size,omitempty"` + GGUF map[string]string `json:"gguf,omitempty"` } // Descriptor provides metadata about the provenance of the model. diff --git a/vendor/github.com/docker/model-distribution/types/model.go b/vendor/github.com/docker/model-distribution/types/model.go index a713ac2f..575a7d0e 100644 --- a/vendor/github.com/docker/model-distribution/types/model.go +++ b/vendor/github.com/docker/model-distribution/types/model.go @@ -13,6 +13,8 @@ type Model interface { } type ModelArtifact interface { - Model + ID() (string, error) + Config() (Config, error) + Descriptor() (Descriptor, error) v1.Image } diff --git a/vendor/github.com/docker/model-runner/pkg/inference/models/manager.go b/vendor/github.com/docker/model-runner/pkg/inference/models/manager.go index 67f8592f..b62c120e 100644 --- a/vendor/github.com/docker/model-runner/pkg/inference/models/manager.go +++ b/vendor/github.com/docker/model-runner/pkg/inference/models/manager.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/docker/model-distribution/distribution" + "github.com/docker/model-distribution/registry" "github.com/docker/model-distribution/types" "github.com/docker/model-runner/pkg/inference" "github.com/docker/model-runner/pkg/logging" @@ -129,17 +130,17 @@ func (m *Manager) handleCreateModel(w http.ResponseWriter, r *http.Request) { // Pull the model. In the future, we may support additional operations here // besides pulling (such as model building). if err := m.PullModel(r.Context(), request.From, w); err != nil { - if errors.Is(err, distribution.ErrInvalidReference) { + if errors.Is(err, registry.ErrInvalidReference) { m.log.Warnf("Invalid model reference %q: %v", request.From, err) http.Error(w, "Invalid model reference", http.StatusBadRequest) return } - if errors.Is(err, distribution.ErrUnauthorized) { + if errors.Is(err, registry.ErrUnauthorized) { m.log.Warnf("Unauthorized to pull model %q: %v", request.From, err) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } - if errors.Is(err, distribution.ErrModelNotFound) { + if errors.Is(err, registry.ErrModelNotFound) { m.log.Warnf("Failed to pull model %q: %v", request.From, err) http.Error(w, "Model not found", http.StatusNotFound) return @@ -388,7 +389,7 @@ func (m *Manager) handlePushModel(w http.ResponseWriter, r *http.Request, model http.Error(w, "Model not found", http.StatusNotFound) return } - if errors.Is(err, distribution.ErrUnauthorized) { + if errors.Is(err, registry.ErrUnauthorized) { m.log.Warnf("Unauthorized to push model %q: %v", model, err) http.Error(w, "Unauthorized", http.StatusUnauthorized) return diff --git a/vendor/github.com/gpustack/gguf-parser-go/.gitattributes b/vendor/github.com/gpustack/gguf-parser-go/.gitattributes new file mode 100644 index 00000000..45b58eb7 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/.gitattributes @@ -0,0 +1,4 @@ +* text=auto eol=lf + +**/go.sum linguist-generated=true +**/zz_generated.*.go linguist-generated=true diff --git a/vendor/github.com/gpustack/gguf-parser-go/.gitignore b/vendor/github.com/gpustack/gguf-parser-go/.gitignore new file mode 100644 index 00000000..03725f24 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/.gitignore @@ -0,0 +1,31 @@ +# Files +.DS_Store +*.lock +*.test +*.out +*.swp +*.swo +*.db +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.log +go.work +go.work.* + +# Dirs +/.idea +/.vscode +/.kube +/.terraform +/.vagrant +/.bundle +/.cache +/.docker +/.entc +/.sbin +/.dist +/log +/certs diff --git a/vendor/github.com/gpustack/gguf-parser-go/.golangci.yaml b/vendor/github.com/gpustack/gguf-parser-go/.golangci.yaml new file mode 100644 index 00000000..480355ee --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/.golangci.yaml @@ -0,0 +1,146 @@ +run: + timeout: 10m + tests: true + modules-download-mode: readonly + go: "1.22" + +# output configuration options +output: + print-issued-lines: true + print-linter-name: true + uniq-by-line: true + path-prefix: "" + sort-results: true + +linters: + disable-all: true + enable: + - asciicheck + - bidichk + - decorder + - durationcheck + - errcheck + - errname + - errorlint + - exportloopref + - godot + - goconst + - gocritic + - gosimple + - gosec + - govet + - gofumpt + - gofmt + - ineffassign + - importas + - lll + - makezero + - misspell + - nakedret + - nilerr + - prealloc + - predeclared + - revive + - staticcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + - whitespace + +linters-settings: + decorder: + dec-order: + - const + - var + - func + disable-init-func-first-check: false + disable-dec-order-check: true + errorlint: + errorf: true + asserts: true + comparison: true + godot: + scope: all + exclude: + - "(?i)^ FIXME:" + - "(?i)^ TODO:" + - "(?i)^ SPDX\\-License\\-Identifier:" + - "(?i)^ +" + period: true + capital: false + goconst: + min-len: 3 + min-occurrences: 10 + gosimple: + checks: [ "all" ] + gosec: + severity: "low" + confidence: "low" + excludes: + - G101 + - G107 + - G112 + - G404 + gofumpt: + extra-rules: true + gofmt: + simplify: true + rewrite-rules: + - pattern: 'interface{}' + replacement: 'any' + - pattern: 'a[b:len(a)]' + replacement: 'a[b:]' + importas: + no-unaliased: true + lll: + line-length: 150 + tab-width: 1 + makezero: + always: false + misspell: + locale: US + nakedret: + max-func-lines: 60 + revive: + rules: + - name: var-naming + disabled: true + arguments: + - [ "HTTP", "ID", "TLS", "TCP", "UDP", "API", "CA", "URL", "DNS" ] + staticcheck: + checks: [ "all", "-SA1019", "-SA2002", "-SA5008" ] + stylecheck: + checks: [ "all", "-ST1003" ] + unparam: + check-exported: false + unused: + field-writes-are-uses: true + post-statements-are-reads: true + exported-is-used: true + exported-fields-are-used: true + parameters-are-used: true + local-variables-are-used: true + generated-is-used: true + usestdlibvars: + http-method: true + http-status-code: true + time-weekday: true + time-month: true + time-layout: true + crypto-hash: true + +issues: + exclude-files: + - "doc.go" + - "zz_generated.*.go" + - "gen.*.go" + exclude-rules: + - path: _test\.go + linters: + - errcheck + - gosec + - makezero + - lll diff --git a/vendor/github.com/gpustack/gguf-parser-go/Dockerfile b/vendor/github.com/gpustack/gguf-parser-go/Dockerfile new file mode 100644 index 00000000..661544c7 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/Dockerfile @@ -0,0 +1,5 @@ +FROM scratch +ARG TARGETOS +ARG TARGETARCH +COPY --chmod=755 .dist/gguf-parser-${TARGETOS}-${TARGETARCH} /bin/gguf-parser +ENTRYPOINT ["/bin/gguf-parser"] diff --git a/vendor/github.com/gpustack/gguf-parser-go/LICENSE b/vendor/github.com/gpustack/gguf-parser-go/LICENSE new file mode 100644 index 00000000..804750ec --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 gguf-parser-go authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/gpustack/gguf-parser-go/Makefile b/vendor/github.com/gpustack/gguf-parser-go/Makefile new file mode 100644 index 00000000..1eea47d6 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/Makefile @@ -0,0 +1,140 @@ +.SILENT: +.DEFAULT_GOAL := ci + +SHELL := /bin/bash + +SRCDIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) +GOOS := $(shell go env GOOS) +GOARCH := $(shell go env GOARCH) +LINT_DIRTY ?= false +VERSION ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "unknown") + +DEPS_UPDATE ?= false +deps: + @echo "+++ $@ +++" + + cd $(SRCDIR) && go mod tidy && go mod download + cd $(SRCDIR)/cmd/gguf-parser && go mod tidy && go mod download + + if [[ "$(DEPS_UPDATE)" == "true" ]]; then \ + cd $(SRCDIR) && go get -u -v ./...; \ + cd $(SRCDIR)/cmd/gguf-parser && go get -u -v ./...; \ + fi + + @echo "--- $@ ---" + +generate: + @echo "+++ $@ +++" + + cd $(SRCDIR) && go generate ./... + cd $(SRCDIR)/cmd/gguf-parser && go generate ./... + + @echo "--- $@ ---" + +lint: + @echo "+++ $@ +++" + + if [[ "$(LINT_DIRTY)" == "true" ]]; then \ + if [[ -n $$(git status --porcelain) ]]; then \ + echo "Code tree is dirty."; \ + git diff --exit-code; \ + fi; \ + fi + + [[ -d "$(SRCDIR)/.sbin" ]] || mkdir -p "$(SRCDIR)/.sbin" + + [[ -f "$(SRCDIR)/.sbin/goimports-reviser" ]] || \ + curl --retry 3 --retry-all-errors --retry-delay 3 -sSfL "https://github.com/incu6us/goimports-reviser/releases/download/v3.6.5/goimports-reviser_3.6.5_$(GOOS)_$(GOARCH).tar.gz" \ + | tar -zxvf - --directory "$(SRCDIR)/.sbin" --no-same-owner --exclude ./LICENSE --exclude ./README.md && chmod +x "$(SRCDIR)/.sbin/goimports-reviser" + cd $(SRCDIR) && \ + go list -f "{{.Dir}}" ./... | xargs -I {} find {} -maxdepth 1 -type f -name '*.go' ! -name 'gen.*' ! -name 'zz_generated.*' \ + | xargs -I {} "$(SRCDIR)/.sbin/goimports-reviser" -use-cache -imports-order=std,general,company,project,blanked,dotted -output=file {} + cd $(SRCDIR)/cmd/gguf-parser && \ + go list -f "{{.Dir}}" ./... | xargs -I {} find {} -maxdepth 1 -type f -name '*.go' ! -name 'gen.*' ! -name 'zz_generated.*' \ + | xargs -I {} "$(SRCDIR)/.sbin/goimports-reviser" -use-cache -imports-order=std,general,company,project,blanked,dotted -output=file {} + + [[ -f "$(SRCDIR)/.sbin/golangci-lint" ]] || \ + curl --retry 3 --retry-all-errors --retry-delay 3 -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \ + | sh -s -- -b "$(SRCDIR)/.sbin" "v1.59.0" + cd $(SRCDIR) && \ + "$(SRCDIR)/.sbin/golangci-lint" run --fix ./... + cd $(SRCDIR)/cmd/gguf-parser && \ + "$(SRCDIR)/.sbin/golangci-lint" run --fix ./... + + @echo "--- $@ ---" + +test: + @echo "+++ $@ +++" + + go test -v -failfast -race -cover -timeout=30m $(SRCDIR)/... + + @echo "--- $@ ---" + +benchmark: + @echo "+++ $@ +++" + + go test -v -failfast -run="^Benchmark[A-Z]+" -bench=. -benchmem -timeout=30m $(SRCDIR)/... + + @echo "--- $@ ---" + +gguf-parser: + [[ -d "$(SRCDIR)/.dist" ]] || mkdir -p "$(SRCDIR)/.dist" + + cd "$(SRCDIR)/cmd/gguf-parser" && for os in darwin linux windows; do \ + tags="netgo"; \ + if [[ $$os == "windows" ]]; then \ + suffix=".exe"; \ + tags="netcgo"; \ + else \ + suffix=""; \ + fi; \ + for arch in amd64 arm64; do \ + echo "Building gguf-parser for $$os-$$arch $(VERSION)"; \ + GOOS="$$os" GOARCH="$$arch" CGO_ENABLED=1 go build \ + -trimpath \ + -ldflags="-w -s -X main.Version=$(VERSION)" \ + -tags="urfave_cli_no_docs $$tags" \ + -o $(SRCDIR)/.dist/gguf-parser-$$os-$$arch$$suffix; \ + done; \ + if [[ $$os == "darwin" ]]; then \ + [[ -d "$(SRCDIR)/.sbin" ]] || mkdir -p "$(SRCDIR)/.sbin"; \ + [[ -f "$(SRCDIR)/.sbin/lipo" ]] || \ + GOBIN="$(SRCDIR)/.sbin" go install github.com/konoui/lipo@v0.9.1; \ + "$(SRCDIR)/.sbin/lipo" -create -output $(SRCDIR)/.dist/gguf-parser-darwin-universal $(SRCDIR)/.dist/gguf-parser-darwin-amd64 $(SRCDIR)/.dist/gguf-parser-darwin-arm64; \ + fi;\ + if [[ $$os == "$(GOOS)" ]] && [[ $$arch == "$(GOARCH)" ]]; then \ + cp -rf $(SRCDIR)/.dist/gguf-parser-$$os-$$arch$$suffix $(SRCDIR)/.dist/gguf-parser$$suffix; \ + fi; \ + done + +build: gguf-parser + +PACKAGE_PUBLISH ?= false +PACKAGE_REGISTRY ?= "gpustack" +PACKAGE_IMAGE ?= "gguf-parser" +package: build + @echo "+++ $@ +++" + + if [[ -z $$(command -v docker) ]]; then \ + echo "Docker is not installed."; \ + exit 1; \ + fi; \ + platform="linux/amd64,linux/arm64"; \ + image="$(PACKAGE_IMAGE):$(VERSION)"; \ + if [[ -n "$(PACKAGE_REGISTRY)" ]]; then \ + image="$(PACKAGE_REGISTRY)/$$image"; \ + fi; \ + if [[ "$(PACKAGE_PUBLISH)" == "true" ]]; then \ + if [[ -z $$(docker buildx inspect --builder "gguf-parser") ]]; then \ + docker run --rm --privileged tonistiigi/binfmt:qemu-v7.0.0 --install $$platform; \ + docker buildx create --name "gguf-parser" --driver "docker-container" --buildkitd-flags "--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host" --bootstrap; \ + fi; \ + docker buildx build --progress=plain --platform=$$platform --builder="gguf-parser" --output="type=image,name=$$image,push=true" "$(SRCDIR)"; \ + else \ + platform="linux/$(GOARCH)"; \ + docker buildx build --progress=plain --platform=$$platform --output="type=docker,name=$$image" "$(SRCDIR)"; \ + fi + + @echo "--- $@ ---" + +ci: deps generate test lint build diff --git a/vendor/github.com/gpustack/gguf-parser-go/README.md b/vendor/github.com/gpustack/gguf-parser-go/README.md new file mode 100644 index 00000000..96c6804c --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/README.md @@ -0,0 +1,1159 @@ +# GGUF Parser + +> tl;dr, Review/Check [GGUF](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md) files and estimate the memory +> usage. + +[![Go Report Card](https://goreportcard.com/badge/github.com/gpustack/gguf-parser-go)](https://goreportcard.com/report/github.com/gpustack/gguf-parser-go) +[![CI](https://img.shields.io/github/actions/workflow/status/gpustack/gguf-parser-go/cmd.yml?label=ci)](https://github.com/gpustack/gguf-parser-go/actions) +[![License](https://img.shields.io/github/license/gpustack/gguf-parser-go?label=license)](https://github.com/gpustack/gguf-parser-go#license) +[![Download](https://img.shields.io/github/downloads/gpustack/gguf-parser-go/total)](https://github.com/gpustack/gguf-parser-go/releases) +[![Docker Pulls](https://img.shields.io/docker/pulls/gpustack/gguf-parser)](https://hub.docker.com/r/gpustack/gguf-parser) +[![Release](https://img.shields.io/github/v/release/gpustack/gguf-parser-go)](https://github.com/gpustack/gguf-parser-go/releases/latest) + +[GGUF](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md) is a file format for storing models for inference +with GGML and executors based on GGML. GGUF is a binary format that is designed for fast loading and saving of models, +and for ease of reading. Models are traditionally developed using PyTorch or another framework, and then converted to +GGUF for use in GGML. + +GGUF Parser helps in reviewing and estimating the usage and maximum tokens per second of a GGUF format model without +download it. + +## Key Features + +- **No File Required**: GGUF Parser uses chunking reading to parse the metadata of remote GGUF file, which means you + don't need to download the entire file and load it. +- **Accurate Prediction**: The evaluation results of GGUF Parser usually deviate from the actual usage by about 100MiB. +- **Quick Verification**: You can provide device metrics to calculate the maximum tokens per second (TPS) without + running the model. +- **Type Screening**: GGUF Parser can distinguish what the GGUF file used for, such as Embedding, Reranking, LoRA, etc. +- **Fast**: GGUF Parser is written in Go, which is fast and efficient. + +## Agenda + +- [Notes](#notes) +- [Installation](#installation) +- [Overview](#overview) + + [Parse](#parse) + * [Local File](#parse-local-file) + * [Remote File](#parse-remote-file) + * [From HuggingFace](#parse-from-huggingface) + * [From ModelScope](#parse-from-modelscope) + * [From Ollama Library](#parse-from-ollama-library) + * [Others](#others) + * [Image Model](#parse-image-model) + * [None Model](#parse-none-model) + + [Estimate](#estimate) + * [Across Multiple GPU devices](#across-multiple-gpu-devices) + * [Maximum Tokens Per Second](#maximum-tokens-per-second) + * [Full Layers Offload (default)](#full-layers-offload-default) + * [Zero Layers Offload](#zero-layers-offload) + * [Specific Layers Offload](#specific-layers-offload) + * [Specific Context Size](#specific-context-size) + * [Enable Flash Attention](#enable-flash-attention) + * [Disable MMap](#disable-mmap) + * [With Adapter](#with-adapter) + * [Get Proper Offload Layers](#get-proper-offload-layers) + +## Notes + +- **Since v0.14.0 (BREAKING CHANGE)**, GGUF Parser parses `*.feed_forward_length` metadata as `[]uint64`, + which means the architecture `feedForwardLength` is a list of integers. +- **Since v0.13.0 (BREAKING CHANGE)**, GGUF Parser can parse files + for [StableDiffusion.Cpp](https://github.com/leejet/stable-diffusion.cpp) or StableDiffusion.Cpp like application. + + [LLaMA Box](https://github.com/gpustack/llama-box) is able to offload different components of the all-in-one model + to different devices, e.g. with `-ts 1,1,1`, GGUF Parser return the usage of Text Encoder Models in 1st device, + VAE Model in 2nd device, and Diffusion Model in 3rd device. +- Experimentally, GGUF Parser can estimate the maximum tokens per second(`MAX TPS`) for a (V)LM model according to the + `--device-metric` options. +- GGUF Parser distinguishes the remote devices from `--tensor-split` via `--rpc`. + + For one host multiple GPU devices, you can use `--tensor-split` to get the estimated memory usage of each GPU. + + For multiple hosts multiple GPU devices, you can use `--tensor-split` and `--rpc` to get the estimated memory + usage of each GPU. Since v0.11.0, `--rpc` flag masks the devices specified by `--tensor-split` in front. +- Table result usage: + + `DISTRIBUTABLE` indicates the GGUF file supports distribution inference or not, if the file doesn't support + distribution inference, you can not offload it + with [RPC servers](https://github.com/ggerganov/llama.cpp/tree/master/examples/rpc). + + `RAM` indicates the system memory usage. + + `VRAM *` indicates the local GPU memory usage. + + `RPC * (V)RAM` indicates the remote memory usage. The kind of memory is determined by which backend the RPC server + uses, check the running logs for more details. + + `UMA` indicates the memory usage of Apple macOS only. `NONUMA` adapts to other cases, including non-GPU devices. + + `LAYERS`(`I`/`T`/`O`) indicates the count for input layers, transformer layers, and output layers. Input layers + are not offloaded at present. + +## Installation + +Install from [releases](https://github.com/gpustack/gguf-parser-go/releases). + +## Overview + +### Parse + +#### Parse Local File + +```shell +$ gguf-parser --path ~/.cache/lm-studio/models/unsloth/DeepSeek-R1-Distill-Qwen-7B-GGUF/DeepSeek-R1-Distill-Qwen-7B-Q4_K_M.gguf ++-------------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+-------------------------+-------+----------------+---------------+----------+------------+----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+-------------------------+-------+----------------+---------------+----------+------------+----------+ +| model | DeepSeek R1 Distill ... | qwen2 | IQ2_XXS/Q4_K_M | true | 4.36 GiB | 7.62 B | 4.91 bpw | ++-------+-------------------------+-------+----------------+---------------+----------+------------+----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 131072 | 3584 | 7 | true | 28 | 28 | 18944 | 0 | 152064 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2.47 MiB | 152064 | N/A | 151646 | 151643 | N/A | N/A | N/A | N/A | 151654 | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+----------------------------------------------+-------------------------------------+ +| ARCH | CONTEXT SIZE | BATCH SIZE (L / P) | FLASH ATTENTION | MMAP LOAD | EMBEDDING ONLY | RERANKING | DISTRIBUTABLE | OFFLOAD LAYERS | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | | | | | | +--------------------+------------+------------+----------------+--------+-----------+ +| | | | | | | | | | | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+--------+-----------+ +| qwen2 | 131072 | 2048 / 512 | Disabled | Enabled | No | Unsupported | Supported | 29 (28 + 1) | Yes | 1 + 0 + 0 | 654.26 MiB | 804.26 MiB | 28 + 1 | 7 GiB | 18.59 GiB | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+--------+-----------+ + +$ # Retrieve the model's metadata via split file, +$ # which needs all split files has been downloaded. +$ gguf-parser --path ~/.cache/lm-studio/models/Qwen/Qwen2.5-7B-Instruct-GGUF/qwen2.5-7b-instruct-q8_0-00001-of-00003.gguf ++-------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+---------------------+-------+--------------+---------------+----------+------------+----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+---------------------+-------+--------------+---------------+----------+------------+----------+ +| model | qwen2.5-7b-instruct | qwen2 | Q8_0 | true | 7.54 GiB | 7.62 B | 8.50 bpw | ++-------+---------------------+-------+--------------+---------------+----------+------------+----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 131072 | 3584 | 7 | true | 28 | 28 | 18944 | 0 | 152064 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2.47 MiB | 152064 | N/A | 151643 | 151645 | N/A | N/A | N/A | N/A | 151643 | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +``` + +#### Parse Remote File + +```shell +$ gguf-parser --url="https://huggingface.co/bartowski/Qwen2.5-72B-Instruct-GGUF/resolve/main/Qwen2.5-72B-Instruct-Q4_K_M.gguf" ++-----------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+----------------------+-------+----------------+---------------+-----------+------------+----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+----------------------+-------+----------------+---------------+-----------+------------+----------+ +| model | Qwen2.5 72B Instruct | qwen2 | IQ2_XXS/Q4_K_M | true | 44.15 GiB | 72.71 B | 5.22 bpw | ++-------+----------------------+-------+----------------+---------------+-----------+------------+----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 32768 | 8192 | 8 | true | 64 | 80 | 29568 | 0 | 152064 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2.47 MiB | 152064 | N/A | 151643 | 151645 | N/A | N/A | N/A | N/A | 151643 | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+----------------------------------------------+-------------------------------------+ +| ARCH | CONTEXT SIZE | BATCH SIZE (L / P) | FLASH ATTENTION | MMAP LOAD | EMBEDDING ONLY | RERANKING | DISTRIBUTABLE | OFFLOAD LAYERS | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | | | | | | +--------------------+------------+------------+----------------+--------+-----------+ +| | | | | | | | | | | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+--------+-----------+ +| qwen2 | 32768 | 2048 / 512 | Disabled | Enabled | No | Unsupported | Supported | 81 (80 + 1) | Yes | 1 + 0 + 0 | 403.39 MiB | 553.39 MiB | 80 + 1 | 10 GiB | 57.87 GiB | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+--------+-----------+ + +$ # Retrieve the model's metadata via split file + +$ gguf-parser --url="https://huggingface.co/unsloth/DeepSeek-R1-GGUF/resolve/main/DeepSeek-R1-UD-IQ1_S/DeepSeek-R1-UD-IQ1_S-00001-of-00003.gguf" ++----------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+------------------+-----------+--------------+---------------+------------+------------+----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+------------------+-----------+--------------+---------------+------------+------------+----------+ +| model | DeepSeek R1 BF16 | deepseek2 | BF16 | true | 130.60 GiB | 671.03 B | 1.67 bpw | ++-------+------------------+-----------+--------------+---------------+------------+------------+----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 163840 | 7168 | 1 | true | N/A | 61 | 18432 | 256 | 129280 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2.21 MiB | 129280 | N/A | 0 | 1 | N/A | N/A | N/A | N/A | 128815 | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++-----------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------------------------------+--------------------------------------+ +| ARCH | CONTEXT SIZE | BATCH SIZE (L / P) | FLASH ATTENTION | MMAP LOAD | EMBEDDING ONLY | RERANKING | DISTRIBUTABLE | OFFLOAD LAYERS | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | | | | | | +--------------------+-----------+-----------+----------------+------------+--------+ +| | | | | | | | | | | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++-----------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+-----------+-----------+----------------+------------+--------+ +| deepseek2 | 163840 | 2048 / 512 | Disabled | Enabled | No | Unsupported | Supported | 62 (61 + 1) | Yes | 1 + 0 + 0 | 13.01 GiB | 13.16 GiB | 61 + 1 | 762.50 GiB | 1 TB | ++-----------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+-----------+-----------+----------------+------------+--------+ + +``` + +#### Parse From HuggingFace + +> [!NOTE] +> +> Allow using `HF_ENDPOINT` to override the default HuggingFace endpoint: `https://huggingface.co`. + +```shell +$ gguf-parser --hf-repo="bartowski/Qwen2-VL-2B-Instruct-GGUF" --hf-file="Qwen2-VL-2B-Instruct-f16.gguf" --hf-mmproj-file="mmproj-Qwen2-VL-2B-Instruct-f32.gguf" --visual-max-image-size 1344 ++-----------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+----------------------+---------+--------------+---------------+----------+------------+-----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+----------------------+---------+--------------+---------------+----------+------------+-----------+ +| model | Qwen2 VL 2B Instruct | qwen2vl | F16 | true | 2.88 GiB | 1.54 B | 16.00 bpw | ++-------+----------------------+---------+--------------+---------------+----------+------------+-----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 32768 | 1536 | 6 | true | 12 | 28 | 8960 | 0 | 151936 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2.47 MiB | 151936 | N/A | 151643 | 151645 | N/A | N/A | N/A | N/A | 151643 | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++---------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+----------------------------------------------+---------------------------------------+ +| ARCH | CONTEXT SIZE | BATCH SIZE (L / P) | FLASH ATTENTION | MMAP LOAD | EMBEDDING ONLY | RERANKING | DISTRIBUTABLE | OFFLOAD LAYERS | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | | | | | | +--------------------+------------+------------+----------------+----------+-----------+ +| | | | | | | | | | | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++---------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+----------+-----------+ +| qwen2vl | 32768 | 2048 / 512 | Disabled | Enabled | No | Unsupported | Supported | 29 (28 + 1) | Yes | 1 + 0 + 0 | 213.55 MiB | 363.55 MiB | 28 + 1 | 3.35 GiB | 12.60 GiB | ++---------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+----------+-----------+ + +$ # Retrieve the model's metadata via split file + +$ gguf-parser --hf-repo="bartowski/openbuddy-llama3.3-70b-v24.1-131k-GGUF" --hf-file="openbuddy-llama3.3-70b-v24.1-131k-Q4_0.gguf" ++------------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+-------------------------+-------+--------------+---------------+-----------+------------+----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+-------------------------+-------+--------------+---------------+-----------+------------+----------+ +| model | Openbuddy Llama3.3 7... | llama | Q4_0 | true | 37.35 GiB | 70.55 B | 4.55 bpw | ++-------+-------------------------+-------+--------------+---------------+-----------+------------+----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 131072 | 8192 | 8 | true | 64 | 80 | 28672 | 0 | 128256 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2 MiB | 128256 | N/A | 128000 | 128048 | N/A | N/A | N/A | N/A | 128044 | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+-----------------------------------------+-------------------------------------+ +| ARCH | CONTEXT SIZE | BATCH SIZE (L / P) | FLASH ATTENTION | MMAP LOAD | EMBEDDING ONLY | RERANKING | DISTRIBUTABLE | OFFLOAD LAYERS | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | | | | | | +--------------------+---------+----------+----------------+--------+-----------+ +| | | | | | | | | | | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+---------+----------+----------------+--------+-----------+ +| llama | 131072 | 2048 / 512 | Disabled | Enabled | No | Unsupported | Supported | 81 (80 + 1) | Yes | 1 + 0 + 0 | 1.04 GB | 1.11 GiB | 80 + 1 | 40 GiB | 93.36 GiB | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+---------+----------+----------------+--------+-----------+ + +``` + +#### Parse From ModelScope + +> [!NOTE] +> +> Allow using `MS_ENDPOINT` to override the default ModelScope endpoint: `https://modelscope.cn`. + +```shell +$ gguf-parser --ms-repo="unsloth/DeepSeek-R1-Distill-Qwen-7B-GGUF" --ms-file="DeepSeek-R1-Distill-Qwen-7B-F16.gguf" ++-------------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+-------------------------+-------+--------------+---------------+-----------+------------+-----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+-------------------------+-------+--------------+---------------+-----------+------------+-----------+ +| model | DeepSeek R1 Distill ... | qwen2 | F16 | true | 14.19 GiB | 7.62 B | 16.00 bpw | ++-------+-------------------------+-------+--------------+---------------+-----------+------------+-----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 131072 | 3584 | 7 | true | 28 | 28 | 18944 | 0 | 152064 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2.47 MiB | 152064 | N/A | 151646 | 151643 | N/A | N/A | N/A | N/A | 151654 | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+----------------------------------------------+-------------------------------------+ +| ARCH | CONTEXT SIZE | BATCH SIZE (L / P) | FLASH ATTENTION | MMAP LOAD | EMBEDDING ONLY | RERANKING | DISTRIBUTABLE | OFFLOAD LAYERS | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | | | | | | +--------------------+------------+------------+----------------+--------+-----------+ +| | | | | | | | | | | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+--------+-----------+ +| qwen2 | 131072 | 2048 / 512 | Disabled | Enabled | No | Unsupported | Supported | 29 (28 + 1) | Yes | 1 + 0 + 0 | 654.26 MiB | 804.26 MiB | 28 + 1 | 7 GiB | 27.69 GiB | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+--------+-----------+ + +``` + +#### Parse From Ollama Library + +> [!NOTE] +> +> Allow using `--ol-base-url` to override the default Ollama registry endpoint: `https://registry.ollama.ai`. + +```shell +$ gguf-parser --ol-model="llama3.3" ++--------------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+-------------------------+-------+----------------+---------------+-----------+------------+----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+-------------------------+-------+----------------+---------------+-----------+------------+----------+ +| model | Llama 3.1 70B Instru... | llama | IQ2_XXS/Q4_K_M | true | 39.59 GiB | 70.55 B | 4.82 bpw | ++-------+-------------------------+-------+----------------+---------------+-----------+------------+----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 131072 | 8192 | 8 | true | 64 | 80 | 28672 | 0 | 128256 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2 MiB | 128256 | N/A | 128000 | 128009 | N/A | N/A | N/A | N/A | N/A | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+-----------------------------------------+-------------------------------------+ +| ARCH | CONTEXT SIZE | BATCH SIZE (L / P) | FLASH ATTENTION | MMAP LOAD | EMBEDDING ONLY | RERANKING | DISTRIBUTABLE | OFFLOAD LAYERS | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | | | | | | +--------------------+---------+----------+----------------+--------+-----------+ +| | | | | | | | | | | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+---------+----------+----------------+--------+-----------+ +| llama | 131072 | 2048 / 512 | Disabled | Enabled | No | Unsupported | Supported | 81 (80 + 1) | Yes | 1 + 0 + 0 | 1.04 GB | 1.11 GiB | 80 + 1 | 40 GiB | 95.60 GiB | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+---------+----------+----------------+--------+-----------+ + +$ # Ollama Model includes the preset params and other artifacts, like multimodal projectors or LoRA adapters, +$ # you can get the usage of Ollama running by using `--ol-usage` option. + +$ +--------------------------------------------------------------------------------------------------------------+ +| METADATA | ++-------+-------------------------+-------+----------------+---------------+-----------+------------+----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+-------------------------+-------+----------------+---------------+-----------+------------+----------+ +| model | Llama 3.1 70B Instru... | llama | IQ2_XXS/Q4_K_M | true | 39.59 GiB | 70.55 B | 4.82 bpw | ++-------+-------------------------+-------+----------------+---------------+-----------+------------+----------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| MAX CONTEXT LEN | EMBEDDING LEN | EMBEDDING GQA | ATTENTION CAUSAL | ATTENTION HEAD CNT | LAYERS | FEED FORWARD LEN | EXPERT CNT | VOCABULARY LEN | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ +| 131072 | 8192 | 8 | true | 64 | 80 | 28672 | 0 | 128256 | ++-----------------+---------------+---------------+------------------+--------------------+--------+------------------+------------+----------------+ + ++-------------------------------------------------------------------------------------------------------------------------------------------------------+ +| TOKENIZER | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| MODEL | TOKENS SIZE | TOKENS LEN | ADDED TOKENS LEN | BOS TOKEN | EOS TOKEN | EOT TOKEN | EOM TOKEN | UNKNOWN TOKEN | SEPARATOR TOKEN | PADDING TOKEN | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ +| gpt2 | 2 MiB | 128256 | N/A | 128000 | 128009 | N/A | N/A | N/A | N/A | N/A | ++-------+-------------+------------+------------------+-----------+-----------+-----------+-----------+---------------+-----------------+---------------+ + ++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+----------------------------------------------+-----------------------------------------+ +| ARCH | CONTEXT SIZE | BATCH SIZE (L / P) | FLASH ATTENTION | MMAP LOAD | EMBEDDING ONLY | RERANKING | DISTRIBUTABLE | OFFLOAD LAYERS | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | | | | | | +--------------------+------------+------------+----------------+------------+-----------+ +| | | | | | | | | | | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+------------+-----------+ +| llama | 2048 | 2048 / 512 | Disabled | Enabled | No | Unsupported | Supported | 81 (80 + 1) | Yes | 1 + 0 + 0 | 232.08 MiB | 382.08 MiB | 80 + 1 | 640.52 MiB | 40.23 GiB | ++-------+--------------+--------------------+-----------------+-----------+----------------+-------------+---------------+----------------+----------------+--------------------+------------+------------+----------------+------------+-----------+ + +``` + +#### Others + +##### Parse Image Model + +```shell +$ # Parse FLUX.1-dev Model +$ gguf-parser --hf-repo="gpustack/FLUX.1-dev-GGUF" --hf-file="FLUX.1-dev-FP16.gguf" ++----------------------------------------------------------------------------------------------+ +| METADATA | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ +| model | N/A | diffusion | F16 | true | 31.79 GiB | 17 B | 16.06 bpw | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ + ++-------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++----------------+-------------------------------------------------+------------------+ +| DIFFUSION ARCH | CONDITIONERS | AUTOENCODER | ++----------------+-------------------------------------------------+------------------+ +| FLUX.1 | OpenAI CLIP ViT-L/14 (F16), Google T5-xxl (F16) | FLUX.1 VAE (F16) | ++----------------+-------------------------------------------------+------------------+ + ++---------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++--------+-----------------+-------------+---------------+----------------+-------------------------+-----------------------+ +| ARCH | FLASH ATTENTION | MMAP LOAD | DISTRIBUTABLE | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | +------------+------------+-----------+-----------+ +| | | | | | UMA | NONUMA | UMA | NONUMA | ++--------+-----------------+-------------+---------------+----------------+------------+------------+-----------+-----------+ +| flux_1 | Disabled | Unsupported | Supported | Yes | 333.45 MiB | 483.45 MiB | 31.89 GiB | 41.15 GiB | ++--------+-----------------+-------------+---------------+----------------+------------+------------+-----------+-----------+ + +$ # Parse FLUX.1-dev Model without offload Conditioner and Autoencoder +$ gguf-parser --hf-repo="gpustack/FLUX.1-dev-GGUF" --hf-file="FLUX.1-dev-FP16.gguf" --clip-on-cpu --vae-on-cpu ++----------------------------------------------------------------------------------------------+ +| METADATA | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ +| model | N/A | diffusion | F16 | true | 31.79 GiB | 17 B | 16.06 bpw | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ + ++-------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++----------------+-------------------------------------------------+------------------+ +| DIFFUSION ARCH | CONDITIONERS | AUTOENCODER | ++----------------+-------------------------------------------------+------------------+ +| FLUX.1 | OpenAI CLIP ViT-L/14 (F16), Google T5-xxl (F16) | FLUX.1 VAE (F16) | ++----------------+-------------------------------------------------+------------------+ + ++-------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++--------+-----------------+-------------+---------------+----------------+-----------------------+-----------------------+ +| ARCH | FLASH ATTENTION | MMAP LOAD | DISTRIBUTABLE | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | +-----------+-----------+-----------+-----------+ +| | | | | | UMA | NONUMA | UMA | NONUMA | ++--------+-----------------+-------------+---------------+----------------+-----------+-----------+-----------+-----------+ +| flux_1 | Disabled | Unsupported | Supported | Yes | 16.43 GiB | 16.58 GiB | 22.29 GiB | 25.05 GiB | ++--------+-----------------+-------------+---------------+----------------+-----------+-----------+-----------+-----------+ + +$ # Parse FLUX.1-dev Model with Autoencoder tiling +$ gguf-parser --hf-repo="gpustack/FLUX.1-dev-GGUF" --hf-file="FLUX.1-dev-FP16.gguf" --vae-tiling ++----------------------------------------------------------------------------------------------+ +| METADATA | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ +| model | N/A | diffusion | F16 | true | 31.79 GiB | 17 B | 16.06 bpw | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ + ++-------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++----------------+-------------------------------------------------+------------------+ +| DIFFUSION ARCH | CONDITIONERS | AUTOENCODER | ++----------------+-------------------------------------------------+------------------+ +| FLUX.1 | OpenAI CLIP ViT-L/14 (F16), Google T5-xxl (F16) | FLUX.1 VAE (F16) | ++----------------+-------------------------------------------------+------------------+ + ++---------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++--------+-----------------+-------------+---------------+----------------+-------------------------+-----------------------+ +| ARCH | FLASH ATTENTION | MMAP LOAD | DISTRIBUTABLE | FULL OFFLOADED | RAM | VRAM 0 | +| | | | | +------------+------------+-----------+-----------+ +| | | | | | UMA | NONUMA | UMA | NONUMA | ++--------+-----------------+-------------+---------------+----------------+------------+------------+-----------+-----------+ +| flux_1 | Disabled | Unsupported | Supported | Yes | 333.45 MiB | 483.45 MiB | 31.89 GiB | 36.28 GiB | ++--------+-----------------+-------------+---------------+----------------+------------+------------+-----------+-----------+ + +$ # Parse FLUX.1-dev Model with multiple devices offloading +$ # Support by LLaMA Box v0.0.106+, https://github.com/gpustack/llama-box. +$ gguf-parser --hf-repo="gpustack/FLUX.1-dev-GGUF" --hf-file="FLUX.1-dev-FP16.gguf" --tensor-split="1,1,1" ++----------------------------------------------------------------------------------------------+ +| METADATA | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ +| model | N/A | diffusion | F16 | true | 31.79 GiB | 17 B | 16.06 bpw | ++-------+------+-----------+--------------+---------------+-----------+------------+-----------+ + ++-------------------------------------------------------------------------------------+ +| ARCHITECTURE | ++----------------+-------------------------------------------------+------------------+ +| DIFFUSION ARCH | CONDITIONERS | AUTOENCODER | ++----------------+-------------------------------------------------+------------------+ +| FLUX.1 | OpenAI CLIP ViT-L/14 (F16), Google T5-xxl (F16) | FLUX.1 VAE (F16) | ++----------------+-------------------------------------------------+------------------+ + ++-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++--------+-----------------+-------------+---------------+----------------+-------------------------+---------------------+---------------------+-----------------------+ +| ARCH | FLASH ATTENTION | MMAP LOAD | DISTRIBUTABLE | FULL OFFLOADED | RAM | VRAM 0 | VRAM 1 | VRAM 2 | +| | | | | +------------+------------+----------+----------+------------+--------+-----------+-----------+ +| | | | | | UMA | NONUMA | UMA | NONUMA | UMA | NONUMA | UMA | NONUMA | ++--------+-----------------+-------------+---------------+----------------+------------+------------+----------+----------+------------+--------+-----------+-----------+ +| flux_1 | Disabled | Unsupported | Supported | Yes | 333.45 MiB | 483.45 MiB | 9.34 GiB | 9.60 GiB | 259.96 MiB | 7 GiB | 22.29 GiB | 25.05 GiB | ++--------+-----------------+-------------+---------------+----------------+------------+------------+----------+----------+------------+--------+-----------+-----------+ + +``` + +##### Parse None Model + +```shell +$ # Parse Multi-Modal Projector +$ gguf-parser --hf-repo="bartowski/Qwen2-VL-72B-Instruct-GGUF" --hf-file="mmproj-Qwen2-VL-72B-Instruct-f16.gguf" ++---------------------------------------------------------------------------------------------------------------+ +| METADATA | ++-----------+-------------------------+------+--------------+---------------+----------+------------+-----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++-----------+-------------------------+------+--------------+---------------+----------+------------+-----------+ +| projector | Qwen2-VL-72B-Instruc... | clip | F16 | true | 1.30 GiB | 699.36 M | 16.01 bpw | ++-----------+-------------------------+------+--------------+---------------+----------+------------+-----------+ + ++----------------------------------------------------------------------+ +| ARCHITECTURE | ++----------------+---------------+--------+------------------+---------+ +| PROJECTOR TYPE | EMBEDDING LEN | LAYERS | FEED FORWARD LEN | ENCODER | ++----------------+---------------+--------+------------------+---------+ +| qwen2vl_merger | 1280 | 32 | 0 | Vision | ++----------------+---------------+--------+------------------+---------+ + +$ # Parse LoRA Adapter +$ gguf-parser --hf-repo="ngxson/test_gguf_lora_adapter" --hf-file="lora-Llama-3-Instruct-abliteration-LoRA-8B-f16.gguf" ++---------------------------------------------------------------------------------------------+ +| METADATA | ++---------+------+-------+--------------+---------------+------------+------------+-----------+ +| TYPE | NAME | ARCH | QUANTIZATION | LITTLE ENDIAN | SIZE | PARAMETERS | BPW | ++---------+------+-------+--------------+---------------+------------+------------+-----------+ +| adapter | N/A | llama | F16 | true | 168.08 MiB | 88.12 M | 16.00 bpw | ++---------+------+-------+--------------+---------------+------------+------------+-----------+ + ++---------------------------+ +| ARCHITECTURE | ++--------------+------------+ +| ADAPTER TYPE | LORA ALPHA | ++--------------+------------+ +| lora | 32 | ++--------------+------------+ + +``` + +### Estimate + +#### Across Multiple GPU Devices + +Imaging you're preparing to run +the [hierholzer/Llama-3.1-70B-Instruct-GGUF](https://huggingface.co/hierholzer/Llama-3.1-70B-Instruct-GGUF) model file +across several hosts in your local network. Some of these hosts are equipped with GPU devices, while others do not have +any GPU capabilities. + +```mermaid +flowchart TD + subgraph host4["Windows 11 (host4)"] + ram40(["11GiB RAM remaining"]) + end + subgraph host3["Apple macOS (host3)"] + gpu10["Apple M1 Max (6GiB VRAM remaining)"] + end + subgraph host2["Windows 11 (host2)"] + gpu20["NVIDIA 4090 (12GiB VRAM remaining)"] + end + subgraph host1["Ubuntu (host1)"] + gpu30["NVIDIA 4080 0 (8GiB VRAM remaining)"] + gpu31["NVIDIA 4080 1 (10GiB VRAM remaining)"] + end +``` + +##### Single Host Multiple GPU Devices + +Let's assume you plan to run the model on `host1` only. + +```mermaid +flowchart TD + subgraph host1["Ubuntu (host1)"] + gpu30["NVIDIA 4080 0 (8GiB VRAM remaining)"] + gpu31["NVIDIA 4080 1 (10GiB VRAM remaining)"] + end +``` + +```shell +$ gguf-parser --hf-repo="hierholzer/Llama-3.1-70B-Instruct-GGUF" --hf-file="Llama-3.1-70B-Instruct-Q4_K_M.gguf" --skip-metadata --skip-architecture --skip-tokenizer --ctx-size=1024 --tensor-split="8,10" --in-short ++------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+--------------------------------------+----------------------------------------+ +| RAM | VRAM 0 | VRAM 1 | ++--------------------+------------+------------+----------------+---------+-----------+----------------+-----------+-----------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+---------+-----------+----------------+-----------+-----------+ +| 1 + 0 + 0 | 238.08 MiB | 388.08 MiB | 36 + 0 | 144 MiB | 17.83 GiB | 44 + 1 | 22.01 GiB | 22.57 GiB | ++--------------------+------------+------------+----------------+---------+-----------+----------------+-----------+-----------+ + +``` + +Based on the output provided, serving the `hierholzer/Llama-3.1-70B-Instruct-GGUF` model on `host1` has the following +resource consumption: + +| Host | Available RAM | Request RAM | Available VRAM | Request VRAM | Result | +|-----------------------|---------------|-------------|----------------|--------------|------------| +| host1 | ENOUGH | 388.08 MiB | | | :thumbsup: | +| host1 (NVIDIA 4080 0) | | | 8 GiB | 17.79 GiB | | +| host1 (NVIDIA 4080 1) | | | 10 GiB | 22.51 GiB | | + +It appears that running the model on `host1` alone is not feasible. + +##### Multiple Hosts Multiple GPU Devices + +Next, let's consider the scenario where you plan to run the model on `host4`, while offloading all layers to `host1`, +`host2`, +and `host3`. + +```mermaid +flowchart TD + host4 -->|TCP| gpu10 + host4 -->|TCP| gpu20 + host4 -->|TCP| gpu30 + host4 -->|TCP| gpu31 + + subgraph host4["Windows 11 (host4)"] + ram40(["11GiB RAM remaining"]) + end + subgraph host3["Apple macOS (host3)"] + gpu10["Apple M1 Max (6GiB VRAM remaining)"] + end + subgraph host2["Windows 11 (host2)"] + gpu20["NVIDIA 4090 (12GiB VRAM remaining)"] + end + subgraph host1["Ubuntu (host1)"] + gpu30["NVIDIA 4080 0 (8GiB VRAM remaining)"] + gpu31["NVIDIA 4080 1 (10GiB VRAM remaining)"] + end +``` + +```shell +$ gguf-parser --hf-repo="hierholzer/Llama-3.1-70B-Instruct-GGUF" --hf-file="Llama-3.1-70B-Instruct-Q4_K_M.gguf" --skip-metadata --skip-architecture --skip-tokenizer --ctx-size=1024 --tensor-split="8,10,12,6" --rpc="host1:50052,host1:50053,host2:50052,host3:50052" --in-short ++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+----------------------------------------------+----------------------------------------------+----------------------------------------------+----------------------------------------------+ +| RAM | RPC 0 (V)RAM | RPC 1 (V)RAM | RPC 2 (V)RAM | RPC 3 (V)RAM | ++--------------------+------------+------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+ +| 1 + 0 + 0 | 238.08 MiB | 388.08 MiB | 18 + 0 | 8.85 GiB | 9.28 GiB | 23 + 0 | 10.88 GiB | 11.32 GiB | 27 + 0 | 12.75 GiB | 13.19 GiB | 12 + 1 | 6.87 GiB | 7.38 GiB | ++--------------------+------------+------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+ + +``` + +According to the output provided, serving the `hierholzer/Llama-3.1-70B-Instruct-GGUF` model on `host4` results in the +following resource consumption: + +| Host | Available RAM | Request RAM | Available VRAM | Request VRAM | Result | +|-----------------------|---------------|-------------|----------------|--------------|------------| +| host4 | 11 GiB | 388.08 MiB | | | :thumbsup: | +| host1 (NVIDIA 4080 0) | | | 8 GiB | 9.28 GiB | | +| host1 (NVIDIA 4080 1) | | | 10 GiB | 11.32 GiB | | +| host2 (NVIDIA 4090) | | | 12 GiB | 13.19 GiB | | +| host3 (Apple M1 Max) | ENOUGH | | 6 GiB | 6.87 GiB | | + +It seems that the model cannot be served on `host4`, even with all layers offloaded to `host1`, `host2`, and `host3`. + +We should consider a different approach: running the model on `host3` while offloading all layers to `host1`, `host2`, +and `host4`. + +```mermaid +flowchart TD + host3 -->|TCP| ram40 + host3 -->|TCP| gpu20 + host3 -->|TCP| gpu30 + host3 -->|TCP| gpu31 + + subgraph host4["Windows 11 (host4)"] + ram40(["11GiB RAM remaining"]) + end + subgraph host3["Apple macOS (host3)"] + gpu10["Apple M1 Max (6GiB VRAM remaining)"] + end + subgraph host2["Windows 11 (host2)"] + gpu20["NVIDIA 4090 (12GiB VRAM remaining)"] + end + subgraph host1["Ubuntu (host1)"] + gpu30["NVIDIA 4080 0 (8GiB VRAM remaining)"] + gpu31["NVIDIA 4080 1 (10GiB VRAM remaining)"] + end +``` + +```shell +$ gguf-parser --hf-repo="hierholzer/Llama-3.1-70B-Instruct-GGUF" --hf-file="Llama-3.1-70B-Instruct-Q4_K_M.gguf" --skip-metadata --skip-architecture --skip-tokenizer --ctx-size=1024 --tensor-split="11,12,8,10,6" --rpc="host4:50052,host2:50052,host1:50052,host1:50053" --in-short ++----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+----------------------------------------------+----------------------------------------------+----------------------------------------------+----------------------------------------------+---------------------------------------+ +| RAM | RPC 0 (V)RAM | RPC 1 (V)RAM | RPC 2 (V)RAM | RPC 3 (V)RAM | VRAM 0 | ++--------------------+------------+------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+-----------+----------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+-----------+----------+ +| 1 + 0 + 0 | 238.08 MiB | 388.08 MiB | 19 + 0 | 9.36 GiB | 9.79 GiB | 21 + 0 | 9.92 GiB | 10.35 GiB | 14 + 0 | 6.57 GiB | 7.01 GiB | 17 + 0 | 8.11 GiB | 8.54 GiB | 9 + 1 | 36.52 MiB | 5.91 GiB | ++--------------------+------------+------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+--------------+--------------+----------------+-----------+----------+ + +``` + +According to the output provided, serving the `hierholzer/Llama-3.1-70B-Instruct-GGUF` model on `host3` results in the +following resource consumption: + +| Host | Available RAM | Request RAM | Available VRAM | Request VRAM | Result | +|-----------------------|---------------|-------------|----------------|--------------|------------| +| host3 (Apple M1 Max) | ENOUGH | 238.08 MiB | | | :thumbsup: | +| host4 | 11 GiB | 9.79 GiB | | | :thumbsup: | +| host2 (NVIDIA 4090) | | | 12 GiB | 10.36 GiB | :thumbsup: | +| host1 (NVIDIA 4080 0) | | | 8 GiB | 7.01 GiB | :thumbsup: | +| host1 (NVIDIA 4080 1) | | | 10 GiB | 8.54 GiB | :thumbsup: | +| host3 (Apple M1 Max) | | | 6 GiB | 36.52 MiB | :thumbsup: | + +Now, the model can be successfully served on `host3`, with all layers offloaded to `host1`, `host2`, and `host4`. + +#### Maximum Tokens Per Second + +The maximum TPS estimation for the GGUF Parser is determined by the model's parameter size, context size, model +offloaded layers, and devices on which the model runs. Among these factors, the device's specifications are particularly +important. + +Inspired +by [LLM inference speed of light](https://zeux.io/2024/03/15/llm-inference-sol/), GGUF Parser use the **FLOPS** and +**bandwidth** of the device as evaluation metrics: + +- When the device is a CPU, FLOPS refers to the performance of that CPU, while bandwidth corresponds to the DRAM + bandwidth. +- When the device is a (i)GPU, FLOPS indicates the performance of that (i)GPU, and bandwidth corresponds to the VRAM + bandwidth. +- When the device is a specific host, FLOPS depends on whether the CPU or (i)GPU of that host is being used, while + bandwidth corresponds to the bandwidth connecting the main node to that host. **After all, a chain is only as strong + as + its weakest link.** If the connection bandwidth between the + main node and the host is equal to or greater than the *RAM bandwidth, then the bandwidth should be taken as the *RAM + bandwidth value. + +##### CPU FLOPS Calculation + +The performance of a single CPU cache can be calculated using the following formula: + +$$ CPU\ FLOPS = Number\ of \ Cores \times Core\ Frequency \times Floating\ Point\ Operations\ per\ Cycle $$ + +The Apple M1 Max CPU features a total of 10 cores, consisting of 8 performance cores and 2 efficiency cores. The +performance cores operate at a clock speed of 3.2 GHz, while the efficiency cores run at 2.2 GHz. All cores support +the [ARM NEON instruction set](https://en.wikipedia.org/wiki/ARM_architecture_family#Advanced_SIMD_(Neon)), which +enables 128-bit SIMD operations, allowing multiple floating-point numbers to be processed simultaneously within a +single CPU cycle. Specifically, using single-precision (32-bit) floating-point numbers, each cycle can handle 4 +floating-point operations. + +The peak floating-point performance for a single performance core is calculated as follows: + +$$ Peak\ Performance = 3.2\ GHz \times 4\ FLOPS = 12.8\ GFLOPS $$ + +For a single efficiency core, the calculation is: + +$$ Peak\ Performance = 2.2\ GHz \times 4\ FLOPS = 8.8\ GFLOPS $$ + +Thus, the overall peak floating-point performance of the entire CPU can be determined by combining the contributions +from both types of cores: + +$$ Peak\ Performance = 8\ Cores \times 12.8\ GFLOPS + 2\ Cores \times 8.8\ GFLOPS = 120\ GFLOPS $$ + +> This results in an average performance of 12 GFLOPS per core. It is evident that the average performance achieved by +> utilizing both performance and efficiency cores is lower than that obtained by exclusively using performance cores. + +##### Run LLaMA2-7B-Chat with Apple Silicon M-series + +Taking [TheBloke/Llama-2-7B-Chat-GGUF](https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF) as an +example and estimate the maximum tokens per second for Apple Silicon M-series using the GGUF Parser. + +```shell +$ # Estimate full offloaded Q8_0 model +$ gguf-parser --hf-repo TheBloke/LLaMA-7b-GGUF --hf-file llama-7b.Q8_0.gguf --skip-metadata --skip-architecture --skip-tokenizer --in-short \ + -c 512 \ + --device-metric ";,;" + +$ # Estimate full offloaded Q4_0 model +$ gguf-parser --hf-repo TheBloke/LLaMA-7b-GGUF --hf-file llama-7b.Q4_0.gguf --skip-metadata --skip-architecture --skip-tokenizer --in-short \ + -c 512 \ + --device-metric ";,;" +``` + +| Variant | CPU FLOPS (Performance Core) | iGPU FLOPS | (V)RAM Bandwidth | Q8_0 Max TPS | Q4_0 Max TPS | +|----------|------------------------------|------------------------|------------------|--------------|--------------| +| M1 | 51.2 GFLOPS (4 cores) | 2.6 TFLOPS (8 cores) | 68.3 GBps | 8.68 | 14.56 | +| M1 Pro | 102.4 GFLOPS (8 cores) | 5.2 TFLOPS (16 cores) | 204.8 GBps | 26.04 | 43.66 | +| M1 Max | 102.4 GFLOPS (8 cores) | 10.4 TFLOPS (32 cores) | 409.6 GBps | 52.08 | 87.31 | +| M1 Ultra | 204.8 GFLOPS (16 cores) | 21 TFLOPS (64 cores) | 819.2 GBps | 104.16 | 174.62 | +| M2 | 56 GFLOPS (4 cores) | 3.6 TFLOPS (10 cores) | 102.4 GBps | 13.02 | 21.83 | +| M2 Pro | 112 GFLOPS (8 cores) | 6.8 TFLOPS (19 cores) | 204.8 GBps | 26.04 | 43.66 | +| M2 Max | 112 GFLOPS (8 cores) | 13.6 TFLOPS (38 cores) | 409.6 GBps | 52.08 | 87.31 | +| M2 Ultra | 224 GFLOPS (16 cores) | 27.2 TFLOPS (76 cores) | 819.2 GBps | 104.16 | 174.62 | +| M3 | 64.96 GFLOPS (4 cores) | 4.1 TFLOPS (10 cores) | 102.4 GBps | 13.02 | 21.83 | +| M3 Pro | 97.44 GFLOPS (6 cores) | 7.4 TFLOPS (18 cores) | 153.6 GBps | 19.53 | 32.74 | +| M3 Max | 194.88 GFLOPS (12 cores) | 16.4 TFLOPS (40 cores) | 409.6 GBps | 52.08 | 87.31 | +| M4 | 70.56 GFLOPS (4 cores) | 4.1 TFLOPS | 120 GBps | 15.26 | 25.58 | + +> References: +> - https://www.cpu-monkey.com/en/cpu_family-apple_m_series +> - https://nanoreview.net/ +> - https://en.wikipedia.org/wiki/Apple_M1#Variants +> - https://en.wikipedia.org/wiki/Apple_M2#Variants +> - https://en.wikipedia.org/wiki/Apple_M3#Variants +> - https://en.wikipedia.org/wiki/Apple_M4#Variants + +You can further verify the above results in [Performance of llama.cpp on Apple Silicon M-series +](https://github.com/ggerganov/llama.cpp/discussions/4167#user-content-fn-1-e9a4caf2848534167e450e18fc4ede7f). + +##### Run LLaMA3.1-405B-Instruct with Apple Mac Studio devices combined with Thunderbolt + +Example +by [leafspark/Meta-Llama-3.1-405B-Instruct-GGUF](https://huggingface.co/leafspark/Meta-Llama-3.1-405B-Instruct-GGUF) +and estimate the maximum tokens per second for three Apple Mac Studio devices combined with Thunderbolt. + +| Device | CPU FLOPS (Performance Core) | iGPU FLOPS | (V)RAM Bandwidth | Thunderbolt Bandwidth | Role | +|-------------------------------|------------------------------|------------------------|------------------|-----------------------|------------| +| Apple Mac Studio (M2 Ultra) 0 | 224 GFLOPS (16 cores) | 27.2 TFLOPS (76 cores) | 819.2 GBps | 40 Gbps | Main | +| Apple Mac Studio (M2 Ultra) 1 | 224 GFLOPS (16 cores) | 27.2 TFLOPS (76 cores) | 819.2 GBps | 40 Gbps | RPC Server | +| Apple Mac Studio (M2 Ultra) 2 | 224 GFLOPS (16 cores) | 27.2 TFLOPS (76 cores) | 819.2 GBps | 40 Gbps | RPC Server | + +Get the maximum tokens per second with the following command: + +```shell +$ # Explain the command: +$ # --device-metric "224GFLOPS;819.2GBps" <-- Apple Mac Studio 0 CPU FLOPS and RAM Bandwidth +$ # --device-metric "27.2TFLOPS;819.2GBps;40Gbps" <-- Apple Mac Studio 1 (RPC 0) iGPU FLOPS, VRAM Bandwidth, and Thunderbolt Bandwidth +$ # --device-metric "27.2TFLOPS;819.2GBps;40Gbps" <-- Apple Mac Studio 2 (RPC 1) iGPU FLOPS, VRAM Bandwidth, and Thunderbolt Bandwidth +$ # --device-metric "27.2TFLOPS;819.2GBps" <-- Apple Mac Studio 0 iGPU FLOPS and VRAM Bandwidth +$ gguf-parser --hf-repo leafspark/Meta-Llama-3.1-405B-Instruct-GGUF --hf-file Llama-3.1-405B-Instruct.Q4_0.gguf/Llama-3.1-405B-Instruct.Q4_0-00001-of-00012.gguf --skip-metadata --skip-architecture --skip-tokenizer --in-short \ + --no-mmap \ + -c 512 \ + --rpc host1:port,host2:port \ + --tensor-split "" \ + --device-metric "224GFLOPS;819.2GBps" \ + --device-metric "27.2TFLOPS;819.2GBps;40Gbps" \ + --device-metric "27.2TFLOPS;819.2GBps;40Gbps" \ + --device-metric "27.2TFLOPS;819.2GBps" +``` + +| Tensor Split | Apple Mac Studio 0 RAM | Apple Mac Studio 1 VRAM (RPC 0) | Apple Mac Studio 2 VRAM (RPC 1) | Apple Mac Studio 0 VRAM | Q4_0 Max TPS | +|--------------|------------------------|---------------------------------|----------------------------------|-------------------------|--------------| +| 1,1,1 | 1.99 GiB | 72.74 GiB | 71.04 GiB | 70.96 GiB | 10.71 | +| 2,1,1 | 1.99 GiB | 108.26 GiB | 54.13 GiB | 52.35 GiB | 11.96 | +| 3,1,1 | 1.99 GiB | 130.25 GiB | 42.29 GiB | 42.20 GiB | 9.10 | +| 4,1,1 | 1.99 GiB | 143.78 GiB | 35.52 GiB | 35.44 GiB | 7.60 | + +##### Run Qwen2.5-72B-Instruct with NVIDIA RTX 4080 and remote RPC by Apple Mac Studio (M2) + +Example by [Qwen/Qwen2.5-72B-Instruct-GGUF](https://huggingface.co/Qwen/Qwen2.5-72B-Instruct-GGUF) and estimate the +maximum tokens per second for NVIDIA RTX 4080. + +| Hardware | FLOPS | Bandwidth | +|---------------------------------------------|--------------|------------| +| Intel i5-14600k | 510.4 GFLOPS | | +| 2 x Corsair Vengeance RGB DDR5-6000 (32GiB) | | 96 GBps | +| 2 x NVIDIA GeForce RTX 4080 | 48.74 TFLOPS | 736.3 GBps | +| Apple Mac Studio (M2) | 27.2 TFLOPS | 819.2 GBps | + +```shell +$ # Explain the command: +$ # --tensor-split 20369,12935,13325 <-- Available Memory in MiB for each device +$ # --device-metric "510.4GFLOPS;96GBps" <-- Intel i5-14600k CPU FLOPS and RAM Bandwidth +$ # --device-metric "27.2TFLOPS;819.2GBps;40Gbps" <-- Apple Mac Studio (M2) (RPC 0) iGPU FLOPS, VRAM Bandwidth, and Thunderbolt Bandwidth +$ # --device-metric "48.74TFLOPS;736.3GBps;64GBps" <-- NVIDIA GeForce RTX 0 4080 GPU FLOPS, VRAM Bandwidth, and PCIe 5.0 x16 Bandwidth +$ # --device-metric "48.74TFLOPS;736.3GBps;8GBps" <-- NVIDIA GeForce RTX 1 4080 GPU FLOPS, VRAM Bandwidth, and PCIe 4.0 x4 Bandwidth +$ gguf-parser --hf-repo Qwen/Qwen2.5-72B-Instruct-GGUF --hf-file qwen2.5-72b-instruct-q4_k_m-00001-of-00012.gguf --skip-metadata --skip-architecture --skip-tokenizer --in-short \ + --no-mmap \ + -c 8192 \ + --rpc host:port \ + --tensor-split 20369,12935,13325 \ + --device-metric "510.4GFLOPS;96GBps" \ + --device-metric "27.2TFLOPS;819.2GBps;40Gbps" \ + --device-metric "48.74TFLOPS;736.3GBps;64GBps" \ + --device-metric "48.74TFLOPS;736.3GBps;8GBps" ++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| ESTIMATE | ++-----------+------------------------------------------+----------------------------------------------+----------------------------------------+----------------------------------------+ +| MAX TPS | RAM | RPC 0 (V)RAM | VRAM 0 | VRAM 1 | +| +--------------------+----------+----------+----------------+--------------+--------------+----------------+-----------+-----------+----------------+-----------+-----------+ +| | LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++-----------+--------------------+----------+----------+----------------+--------------+--------------+----------------+-----------+-----------+----------------+-----------+-----------+ +| 51.82 tps | 1 + 0 + 0 | 1.19 GiB | 1.34 GiB | 36 + 0 | 18.85 GiB | 20.17 GiB | 22 + 0 | 11.34 GiB | 12.66 GiB | 22 + 1 | 12.65 GiB | 13.97 GiB | ++-----------+--------------------+----------+----------+----------------+--------------+--------------+----------------+-----------+-----------+----------------+-----------+-----------+ +``` + +#### Full Layers Offload (default) + +```shell +$ gguf-parser --hf-repo="etemiz/Llama-3.1-405B-Inst-GGUF" --hf-file="llama-3.1-405b-IQ1_M-00019-of-00019.gguf" --skip-metadata --skip-architecture --skip-tokenizer --in-short ++--------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+---------------------------------------+ +| RAM | VRAM 0 | ++--------------------+------------+------------+----------------+---------+------------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 0 + 0 | 652.53 MiB | 802.53 MiB | 126 + 1 | 126 GiB | 246.59 GiB | ++--------------------+------------+------------+----------------+---------+------------+ + +``` + +#### Zero Layers Offload + +```shell +$ gguf-parser --hf-repo="etemiz/Llama-3.1-405B-Inst-GGUF" --hf-file="llama-3.1-405b-IQ1_M-00019-of-00019.gguf" --skip-metadata --skip-architecture --skip-tokenizer --gpu-layers=0 --in-short ++------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+-------------------------------------+ +| RAM | VRAM 0 | ++--------------------+------------+------------+----------------+--------+-----------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+--------+-----------+ +| 1 + 126 + 1 | 126.37 GiB | 126.52 GiB | 0 + 0 | 0 B | 32.34 GiB | ++--------------------+------------+------------+----------------+--------+-----------+ + +``` + +#### Specific Layers Offload + +```shell +$ gguf-parser --hf-repo="etemiz/Llama-3.1-405B-Inst-GGUF" --hf-file="llama-3.1-405b-IQ1_M-00019-of-00019.gguf" --skip-metadata --skip-architecture --skip-tokenizer --gpu-layers=10 --in-short ++------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+-------------------------------------+ +| RAM | VRAM 0 | ++--------------------+------------+------------+----------------+--------+-----------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+--------+-----------+ +| 1 + 116 + 1 | 116.64 GiB | 116.78 GiB | 10 + 0 | 10 GiB | 50.39 GiB | ++--------------------+------------+------------+----------------+--------+-----------+ + +``` + +#### Specific Context Size + +By default, the context size retrieved from the model's metadata. + +Use `--ctx-size` to specify the context size. + +```shell +$ gguf-parser --hf-repo="etemiz/Llama-3.1-405B-Inst-GGUF" --hf-file="llama-3.1-405b-IQ1_M-00019-of-00019.gguf" --skip-metadata --skip-architecture --skip-tokenizer --ctx-size=4096 --in-short ++--------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+---------------------------------------+ +| RAM | VRAM 0 | ++--------------------+------------+------------+----------------+----------+-----------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+----------+-----------+ +| 1 + 0 + 0 | 404.53 MiB | 554.53 MiB | 126 + 1 | 3.94 GiB | 93.28 GiB | ++--------------------+------------+------------+----------------+----------+-----------+ + +``` + +#### Enable Flash Attention + +By default, LLaMA.cpp disables the Flash Attention. + +Enable Flash Attention will reduce the VRAM usage, but it also increases the GPU/CPU usage. + +Use `--flash-attention` to enable the Flash Attention. + +Please note that not all models support Flash Attention, if the model does not support, the "FLASH ATTENTION" shows " +Disabled" even if you enable it. + +```shell +$ gguf-parser --hf-repo="etemiz/Llama-3.1-405B-Inst-GGUF" --hf-file="llama-3.1-405b-IQ1_M-00019-of-00019.gguf" --skip-metadata --skip-architecture --skip-tokenizer --flash-attention --in-short ++--------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+---------------------------------------+ +| RAM | VRAM 0 | ++--------------------+------------+------------+----------------+---------+------------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 0 + 0 | 620.53 MiB | 770.53 MiB | 126 + 1 | 126 GiB | 215.70 GiB | ++--------------------+------------+------------+----------------+---------+------------+ + +``` + +#### Disable MMap + +By default, LLaMA.cpp loads the model via Memory-Mapped. + +For Apple MacOS, Memory-Mapped is an efficient way to load the model, and results in a lower VRAM usage. +For other platforms, Memory-Mapped affects the first-time model loading speed only. + +Use `--no-mmap` to disable loading the model via Memory-Mapped. + +Please note that some models require loading the whole weight into memory, if the model does not support MMap, the "MMAP +LOAD" shows "Not Supported". + +```shell +$ gguf-parser --hf-repo="etemiz/Llama-3.1-405B-Inst-GGUF" --hf-file="llama-3.1-405b-IQ1_M-00019-of-00019.gguf" --skip-metadata --skip-architecture --skip-tokenizer --no-mmap --in-short ++-------------------------------------------------------------------------------------+ +| ESTIMATE | ++------------------------------------------+------------------------------------------+ +| RAM | VRAM 0 | ++--------------------+----------+----------+----------------+------------+------------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+----------+----------+----------------+------------+------------+ +| 1 + 0 + 0 | 1.98 GiB | 2.13 GiB | 126 + 1 | 213.97 GiB | 246.59 GiB | ++--------------------+----------+----------+----------------+------------+------------+ + +``` + +#### With Adapter + +Use `--lora`/`--control-vector` to estimate the usage when loading a model with adapters. + +```shell +$ gguf-parser --hf-repo="QuantFactory/Meta-Llama-3-8B-Instruct-GGUF" --hf-file="Meta-Llama-3-8B-Instruct.Q5_K_M.gguf" --skip-metadata --skip-architecture --skip-tokenizer --in-short ++-----------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+------------------------------------+ +| RAM | VRAM 0 | ++--------------------+------------+------------+----------------+--------+----------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+--------+----------+ +| 1 + 0 + 0 | 163.62 MiB | 313.62 MiB | 32 + 1 | 1 GiB | 6.79 GiB | ++--------------------+------------+------------+----------------+--------+----------+ + +$ # With a LoRA adapter. +$ gguf-parser --hf-repo="QuantFactory/Meta-Llama-3-8B-Instruct-GGUF" --hf-file="Meta-Llama-3-8B-Instruct.Q5_K_M.gguf" --lora-url="https://huggingface.co/ngxson/test_gguf_lora_adapter/resolve/main/lora-Llama-3-Instruct-abliteration-LoRA-8B-f16.gguf" --skip-metadata --skip-architecture --skip-tokenizer --in-short ++-------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+--------------------------------------+ +| RAM | VRAM 0 | ++--------------------+------------+------------+----------------+----------+----------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+----------+----------+ +| 1 + 0 + 0 | 168.64 MiB | 318.64 MiB | 32 + 1 | 1.16 GiB | 6.94 GiB | ++--------------------+------------+------------+----------------+----------+----------+ + +``` + +#### Get Proper Offload Layers + +Use `--gpu-layers-step` to get the proper offload layers number when the model is too large to fit into the GPUs memory. + +```shell +$ gguf-parser --hf-repo="etemiz/Llama-3.1-405B-Inst-GGUF" --hf-file="llama-3.1-405b-IQ1_M-00019-of-00019.gguf" --skip-metadata --skip-architecture --skip-tokenizer --gpu-layers-step=6 --in-short ++--------------------------------------------------------------------------------------+ +| ESTIMATE | ++----------------------------------------------+---------------------------------------+ +| RAM | VRAM 0 | ++--------------------+------------+------------+----------------+---------+------------+ +| LAYERS (I + T + O) | UMA | NONUMA | LAYERS (T + O) | UMA | NONUMA | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 126 + 1 | 126.59 GiB | 126.73 GiB | 0 + 0 | 0 B | 250 MiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 120 + 1 | 120.64 GiB | 120.78 GiB | 6 + 0 | 6 GiB | 43.68 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 114 + 1 | 114.64 GiB | 114.78 GiB | 12 + 0 | 12 GiB | 53.74 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 108 + 1 | 108.64 GiB | 108.78 GiB | 18 + 0 | 18 GiB | 63.80 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 102 + 1 | 102.64 GiB | 102.78 GiB | 24 + 0 | 24 GiB | 73.86 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 96 + 1 | 96.64 GiB | 96.78 GiB | 30 + 0 | 30 GiB | 83.93 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 90 + 1 | 90.64 GiB | 90.78 GiB | 36 + 0 | 36 GiB | 93.99 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 84 + 1 | 84.64 GiB | 84.78 GiB | 42 + 0 | 42 GiB | 104.05 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 78 + 1 | 78.64 GiB | 78.78 GiB | 48 + 0 | 48 GiB | 114.11 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 72 + 1 | 72.64 GiB | 72.78 GiB | 54 + 0 | 54 GiB | 124.17 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 66 + 1 | 66.64 GiB | 66.78 GiB | 60 + 0 | 60 GiB | 134.23 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 60 + 1 | 60.64 GiB | 60.78 GiB | 66 + 0 | 66 GiB | 144.29 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 54 + 1 | 54.64 GiB | 54.78 GiB | 72 + 0 | 72 GiB | 154.35 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 48 + 1 | 48.64 GiB | 48.78 GiB | 78 + 0 | 78 GiB | 164.42 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 42 + 1 | 42.64 GiB | 42.78 GiB | 84 + 0 | 84 GiB | 174.48 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 36 + 1 | 36.64 GiB | 36.78 GiB | 90 + 0 | 90 GiB | 184.54 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 30 + 1 | 30.64 GiB | 30.78 GiB | 96 + 0 | 96 GiB | 194.60 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 24 + 1 | 24.64 GiB | 24.78 GiB | 102 + 0 | 102 GiB | 204.66 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 18 + 1 | 18.64 GiB | 18.78 GiB | 108 + 0 | 108 GiB | 214.72 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 12 + 1 | 12.64 GiB | 12.78 GiB | 114 + 0 | 114 GiB | 225.05 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 6 + 1 | 6.64 GiB | 6.78 GiB | 120 + 0 | 120 GiB | 235.64 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 0 + 1 | 653.08 MiB | 803.08 MiB | 126 + 0 | 126 GiB | 246.24 GiB | ++--------------------+------------+------------+----------------+---------+------------+ +| 1 + 0 + 0 | 652.53 MiB | 802.53 MiB | 126 + 1 | 126 GiB | 246.59 GiB | ++--------------------+------------+------------+----------------+---------+------------+ + +``` + +## License + +MIT diff --git a/vendor/github.com/gpustack/gguf-parser-go/cache.go b/vendor/github.com/gpustack/gguf-parser-go/cache.go new file mode 100644 index 00000000..33fd753c --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/cache.go @@ -0,0 +1,105 @@ +package gguf_parser + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/gpustack/gguf-parser-go/util/json" + "github.com/gpustack/gguf-parser-go/util/osx" + "github.com/gpustack/gguf-parser-go/util/stringx" +) + +var ( + ErrGGUFFileCacheDisabled = errors.New("GGUF file cache disabled") + ErrGGUFFileCacheMissed = errors.New("GGUF file cache missed") + ErrGGUFFileCacheCorrupted = errors.New("GGUF file cache corrupted") +) + +type GGUFFileCache string + +func (c GGUFFileCache) getKeyPath(key string) string { + k := stringx.SumByFNV64a(key) + p := filepath.Join(string(c), k[:1], k) + return p +} + +func (c GGUFFileCache) Get(key string, exp time.Duration) (*GGUFFile, error) { + if c == "" { + return nil, ErrGGUFFileCacheDisabled + } + + if key == "" { + return nil, ErrGGUFFileCacheMissed + } + + p := c.getKeyPath(key) + if !osx.Exists(p, func(stat os.FileInfo) bool { + if !stat.Mode().IsRegular() { + return false + } + return exp == 0 || time.Since(stat.ModTime()) < exp + }) { + return nil, ErrGGUFFileCacheMissed + } + + var gf GGUFFile + { + bs, err := os.ReadFile(p) + if err != nil { + return nil, fmt.Errorf("GGUF file cache get: %w", err) + } + if err = json.Unmarshal(bs, &gf); err != nil { + return nil, fmt.Errorf("GGUF file cache get: %w", err) + } + } + if len(gf.Header.MetadataKV) == 0 || len(gf.TensorInfos) == 0 { + _ = os.Remove(p) + return nil, ErrGGUFFileCacheCorrupted + } + + return &gf, nil +} + +func (c GGUFFileCache) Put(key string, gf *GGUFFile) error { + if c == "" { + return ErrGGUFFileCacheDisabled + } + + if key == "" || gf == nil { + return nil + } + + bs, err := json.Marshal(gf) + if err != nil { + return fmt.Errorf("GGUF file cache put: %w", err) + } + + p := c.getKeyPath(key) + if err = osx.WriteFile(p, bs, 0o600); err != nil { + return fmt.Errorf("GGUF file cache put: %w", err) + } + return nil +} + +func (c GGUFFileCache) Delete(key string) error { + if c == "" { + return ErrGGUFFileCacheDisabled + } + + if key == "" { + return ErrGGUFFileCacheMissed + } + + p := c.getKeyPath(key) + if !osx.ExistsFile(p) { + return ErrGGUFFileCacheMissed + } + + if err := os.Remove(p); err != nil { + return fmt.Errorf("GGUF file cache delete: %w", err) + } + return nil +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file.go b/vendor/github.com/gpustack/gguf-parser-go/file.go new file mode 100644 index 00000000..0c6a2e3a --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file.go @@ -0,0 +1,1710 @@ +package gguf_parser + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "regexp" + "strings" + + "golang.org/x/exp/constraints" + + "github.com/gpustack/gguf-parser-go/util/anyx" + "github.com/gpustack/gguf-parser-go/util/bytex" + "github.com/gpustack/gguf-parser-go/util/funcx" + "github.com/gpustack/gguf-parser-go/util/osx" + "github.com/gpustack/gguf-parser-go/util/stringx" +) + +// GGUFFile represents a GGUF file, +// see https://github.com/ggerganov/ggml/blob/master/docs/gguf.md#file-structure. +// +// Compared with the complete GGUF file, +// this structure lacks the tensor data part. +type GGUFFile struct { + /* Basic */ + + // Header is the header of the GGUF file. + Header GGUFHeader `json:"header"` + // TensorInfos are the tensor infos of the GGUF file, + // the size of TensorInfos is equal to `Header.TensorCount`. + TensorInfos GGUFTensorInfos `json:"tensorInfos"` + // Padding is the padding size of the GGUF file, + // which is used to split Header and TensorInfos from tensor data. + Padding int64 `json:"padding"` + // SplitPaddings holds the padding size slice of the GGUF file splits, + // each item represents splitting Header and TensorInfos from tensor data. + // + // The length of SplitPaddings is the number of split files. + SplitPaddings []int64 `json:"splitPaddings,omitempty"` + // TensorDataStartOffset is the offset in bytes of the tensor data in this file. + // + // The offset is the start of the file. + TensorDataStartOffset int64 `json:"tensorDataStartOffset"` + // SplitTensorDataStartOffsets holds the offset slice in bytes of the tensor data of the GGUF file splits, + // each item represents the offset of the tensor data in the split file. + // + // The length of SplitTensorDataStartOffsets is the number of split files. + SplitTensorDataStartOffsets []int64 `json:"splitTensorDataStartOffsets,omitempty"` + + /* Appendix */ + + // Size is the size of the GGUF file, + // if the file is split, the size is the sum of all split files. + Size GGUFBytesScalar `json:"size"` + // SplitSizes holds the size slice of the GGUF file splits, + // each item represents the size of the split file. + // + // The length of SplitSizes is the number of split files. + SplitSizes []GGUFBytesScalar `json:"splitSizes,omitempty"` + // ModelSize is the size of the model when loading. + ModelSize GGUFBytesScalar `json:"modelSize"` + // SplitModelSizes holds the size slice of the model, + // each item represents a size when loading of the split file. + // + // The length of SplitModelSizes is the number of split files. + SplitModelSizes []GGUFBytesScalar `json:"splitModelSizes,omitempty"` + // ModelParameters is the number of the model parameters. + ModelParameters GGUFParametersScalar `json:"modelParameters"` + // ModelBitsPerWeight is the bits per weight of the model, + // which describes how many bits are used to store a weight, + // higher is better. + ModelBitsPerWeight GGUFBitsPerWeightScalar `json:"modelBitsPerWeight"` +} + +// GGUFMagic is a magic number of GGUF file, +// see https://github.com/ggerganov/ggml/blob/master/docs/gguf.md#historical-state-of-affairs. +type GGUFMagic uint32 + +// GGUFMagic constants. +const ( + GGUFMagicGGML GGUFMagic = 0x67676d6c + GGUFMagicGGMF GGUFMagic = 0x67676d66 + GGUFMagicGGJT GGUFMagic = 0x67676a74 + GGUFMagicGGUFLe GGUFMagic = 0x46554747 // GGUF + GGUFMagicGGUFBe GGUFMagic = 0x47475546 // GGUF +) + +// GGUFVersion is a version of GGUF file format, +// see https://github.com/ggerganov/ggml/blob/master/docs/gguf.md#version-history. +type GGUFVersion uint32 + +// GGUFVersion constants. +const ( + GGUFVersionV1 GGUFVersion = iota + 1 + GGUFVersionV2 + GGUFVersionV3 +) + +// GGUFHeader represents the header of a GGUF file. +type GGUFHeader struct { + // Magic is a magic number that announces that this is a GGUF file. + Magic GGUFMagic `json:"magic"` + // Version is a version of the GGUF file format. + Version GGUFVersion `json:"version"` + // TensorCount is the number of tensors in the file. + TensorCount uint64 `json:"tensorCount"` + // MetadataKVCount is the number of key-value pairs in the metadata. + MetadataKVCount uint64 `json:"metadataKVCount"` + // MetadataKV are the key-value pairs in the metadata, + MetadataKV GGUFMetadataKVs `json:"metadataKV"` +} + +// GGUFMetadataValueType is a type of GGUF metadata value, +// see https://github.com/ggerganov/ggml/blob/master/docs/gguf.md#file-structure. +type GGUFMetadataValueType uint32 + +// GGUFMetadataValueType constants. +const ( + GGUFMetadataValueTypeUint8 GGUFMetadataValueType = iota + GGUFMetadataValueTypeInt8 + GGUFMetadataValueTypeUint16 + GGUFMetadataValueTypeInt16 + GGUFMetadataValueTypeUint32 + GGUFMetadataValueTypeInt32 + GGUFMetadataValueTypeFloat32 + GGUFMetadataValueTypeBool + GGUFMetadataValueTypeString + GGUFMetadataValueTypeArray + GGUFMetadataValueTypeUint64 + GGUFMetadataValueTypeInt64 + GGUFMetadataValueTypeFloat64 + _GGUFMetadataValueTypeCount // Unknown +) + +// Types for GGUFMetadataKV. +type ( + // GGUFMetadataKV is a key-value pair in the metadata of a GGUF file. + GGUFMetadataKV struct { + // Key is the key of the metadata key-value pair, + // which is no larger than 64 bytes long. + Key string `json:"key"` + // ValueType is the type of the metadata value. + ValueType GGUFMetadataValueType `json:"valueType"` + // Value is the value of the metadata key-value pair. + Value any `json:"value"` + } + + // GGUFMetadataKVArrayValue is a value of a GGUFMetadataKV with type GGUFMetadataValueTypeArray. + GGUFMetadataKVArrayValue struct { + /* Basic */ + + // Type is the type of the array item. + Type GGUFMetadataValueType `json:"type"` + // Len is the length of the array. + Len uint64 `json:"len"` + // Array holds all array items. + Array []any `json:"array,omitempty"` + + /* Appendix */ + + // StartOffset is the offset in bytes of the GGUFMetadataKVArrayValue in the GGUFFile file. + // + // The offset is the start of the file. + StartOffset int64 `json:"startOffset"` + + // Size is the size of the array in bytes. + Size int64 `json:"size"` + } + + // GGUFMetadataKVs is a list of GGUFMetadataKV. + GGUFMetadataKVs []GGUFMetadataKV +) + +// Types for GGUFTensorInfo. +type ( + // GGUFTensorInfo represents a tensor info in a GGUF file. + GGUFTensorInfo struct { + /* Basic */ + + // Name is the name of the tensor, + // which is no larger than 64 bytes long. + Name string `json:"name"` + // NDimensions is the number of dimensions of the tensor. + NDimensions uint32 `json:"nDimensions"` + // Dimensions is the dimensions of the tensor, + // the length is NDimensions. + Dimensions []uint64 `json:"dimensions"` + // Type is the type of the tensor. + Type GGMLType `json:"type"` + // Offset is the offset in bytes of the tensor's data in this file. + // + // The offset is relative to tensor data, not to the start of the file. + Offset uint64 `json:"offset"` + + /* Appendix */ + + // StartOffset is the offset in bytes of the GGUFTensorInfo in the GGUFFile file. + // + // The offset is the start of the file. + StartOffset int64 `json:"startOffset"` + } + + // GGUFTensorInfos is a list of GGUFTensorInfo. + GGUFTensorInfos []GGUFTensorInfo +) + +var ErrGGUFFileInvalidFormat = errors.New("invalid GGUF format") + +// ParseGGUFFile parses a GGUF file from the local given path, +// and returns the GGUFFile, or an error if any. +func ParseGGUFFile(path string, opts ...GGUFReadOption) (*GGUFFile, error) { + var o _GGUFReadOptions + for _, opt := range opts { + opt(&o) + } + + var paths []string + { + rs := CompleteShardGGUFFilename(path) + if rs != nil { + paths = rs + } else { + paths = []string{path} + } + } + + fs := make([]_GGUFFileReadSeeker, 0, len(paths)) + defer func() { + for i := range fs { + osx.Close(fs[i]) + } + }() + + for i := range paths { + if o.MMap { + mf, err := osx.OpenMmapFile(paths[i]) + if err != nil { + return nil, fmt.Errorf("open mmap file: %w", err) + } + + fs = append(fs, _GGUFFileReadSeeker{ + Closer: mf, + ReadSeeker: io.NewSectionReader(mf, 0, mf.Len()), + Size: mf.Len(), + }) + + continue + } + + ff, err := osx.Open(paths[i]) + if err != nil { + return nil, fmt.Errorf("open file: %w", err) + } + + fs = append(fs, _GGUFFileReadSeeker{ + Closer: ff, + ReadSeeker: ff, + Size: funcx.MustNoError(ff.Stat()).Size(), + }) + } + + return parseGGUFFile(fs, o) +} + +type _GGUFFileReadSeeker struct { + io.Closer + io.ReadSeeker + Size int64 +} + +func parseGGUFFile(fs []_GGUFFileReadSeeker, o _GGUFReadOptions) (_ *GGUFFile, err error) { + var gf GGUFFile + + for _, f := range fs { + var bo binary.ByteOrder = binary.LittleEndian + + // magic + var magic GGUFMagic + if err = binary.Read(f, bo, &magic); err != nil { + return nil, fmt.Errorf("read magic: %w", err) + } + switch magic { + default: + return nil, ErrGGUFFileInvalidFormat + case GGUFMagicGGML, GGUFMagicGGMF, GGUFMagicGGJT: + return nil, fmt.Errorf("unsupported format: %s", magic) + case GGUFMagicGGUFLe: + case GGUFMagicGGUFBe: + bo = binary.BigEndian + } + gf.Header.Magic = magic + + // version + var version GGUFVersion + if err = binary.Read(f, bo, &version); err != nil { + return nil, fmt.Errorf("read version: %w", err) + } + gf.Header.Version = version + + rd := _GGUFReader{v: version, o: o, f: f, bo: bo} + + // tensor count + var tensorCount uint64 + if version <= GGUFVersionV1 { + tensorCount, err = rd.ReadUint64FromUint32() + } else { + tensorCount, err = rd.ReadUint64() + } + if err != nil { + return nil, fmt.Errorf("read tensor count: %w", err) + } + gf.Header.TensorCount += tensorCount + + // metadata kv count + var metadataKVCount uint64 + if version <= GGUFVersionV1 { + metadataKVCount, err = rd.ReadUint64FromUint32() + } else { + metadataKVCount, err = rd.ReadUint64() + } + if err != nil { + return nil, fmt.Errorf("read metadata kv count: %w", err) + } + gf.Header.MetadataKVCount += metadataKVCount + + // metadata kv + { + rd := _GGUFMetadataReader{_GGUFReader: rd} + kvs := make(GGUFMetadataKVs, metadataKVCount) + for i := uint64(0); i < metadataKVCount; i++ { + kvs[i], err = rd.Read() + if err != nil { + return nil, fmt.Errorf("read metadata kv %d: %w", i, err) + } + } + for i := range kvs { + if kvs[i].Key == "split.no" { + gf.Header.MetadataKVCount-- + continue + } + gf.Header.MetadataKV = append(gf.Header.MetadataKV, kvs[i]) + } + } + + // tensor infos + if gf.TensorInfos == nil { + tc, ok := gf.Header.MetadataKV.Get("split.tensors.count") + if ok { + gf.TensorInfos = make(GGUFTensorInfos, 0, anyx.Number[int](tc.Value)) + } else { + gf.TensorInfos = make(GGUFTensorInfos, 0, tensorCount) + } + } + { + rd := _GGUFTensorInfoReader{_GGUFReader: rd} + tis := make(GGUFTensorInfos, tensorCount) + for i := uint64(0); i < tensorCount; i++ { + tis[i], err = rd.Read() + if err != nil { + return nil, fmt.Errorf("read tensor info %d: %w", i, err) + } + } + gf.TensorInfos = append(gf.TensorInfos, tis...) + } + + pds, err := f.Seek(0, io.SeekCurrent) + if err != nil { + return nil, fmt.Errorf("seek padding start: %w", err) + } + + // padding + var padding int64 + { + // The global alignment to use, as described above. + // This can vary to allow for different alignment schemes, but it must be a multiple of 8. + // Some writers may not write the alignment. + // If the alignment is not specified, assume it is 32. + var ag uint32 = 32 + if v, ok := gf.Header.MetadataKV.Get("general.alignment"); ok { + ag = v.ValueUint32() + } + padding = int64(ag) - (pds % int64(ag)) + } + if len(fs) == 1 { + gf.Padding = padding + } + gf.SplitPaddings = append(gf.SplitPaddings, padding) + + // tensor data offset + tensorDataStartOffset := pds + padding + if len(fs) == 1 { + gf.TensorDataStartOffset = tensorDataStartOffset + } + gf.SplitTensorDataStartOffsets = append(gf.SplitTensorDataStartOffsets, tensorDataStartOffset) + + // size + size := GGUFBytesScalar(f.Size) + gf.Size += size + gf.SplitSizes = append(gf.SplitSizes, size) + + // model size + modelSize := GGUFBytesScalar(f.Size - tensorDataStartOffset) + gf.ModelSize += modelSize + gf.SplitModelSizes = append(gf.SplitModelSizes, modelSize) + } + + // model parameters + gf.ModelParameters = GGUFParametersScalar(gf.TensorInfos.Elements()) + + // bpw + if gf.ModelParameters != 0 { + gf.ModelBitsPerWeight = GGUFBitsPerWeightScalar(float64(gf.ModelSize) * 8 / float64(gf.ModelParameters)) + } + + return &gf, nil +} + +// Types for GGUF hierarchical tensors. +type ( + // IGGUFTensorInfos is an interface for GGUF tensor infos, + // which includes basic operations. + IGGUFTensorInfos interface { + // Get returns the GGUFTensorInfo with the given name, + // and true if found, and false otherwise. + Get(name string) (info GGUFTensorInfo, found bool) + // GetFileType returns the GGUFFileType. + GetFileType() GGUFFileType + // Match returns true if the name matches the given regex, and false otherwise. + Match(nameRegex *regexp.Regexp) bool + // Search returns a list of GGUFTensorInfo with the names that match the given regex. + Search(nameRegex *regexp.Regexp) (infos []GGUFTensorInfo) + // Index returns a map value to the GGUFTensorInfo with the given names, + // and the number of names found. + Index(names []string) (infos map[string]GGUFTensorInfo, found int) + // Elements returns the number of elements(parameters). + Elements() uint64 + // Bytes returns the number of bytes. + Bytes() uint64 + // Count returns the number of tensors. + Count() uint64 + } + + // GGUFLayerTensorInfos represents hierarchical tensor infos of a GGUF file, + // it can save GGUFNamedTensorInfos, GGUFTensorInfos, and GGUFTensorInfo. + GGUFLayerTensorInfos []IGGUFTensorInfos + + // GGUFNamedTensorInfos is the namespace for relevant tensors, + // which must has a name. + GGUFNamedTensorInfos struct { + // Name is the name of the namespace. + Name string `json:"name"` + // GGUFLayerTensorInfos can save GGUFNamedTensorInfos, GGUFTensorInfos, or GGUFTensorInfo. + // + // If the item is type of GGUFTensorInfo, it must be the leaf node. + // + // Any branch nodes are type of GGUFNamedTensorInfos or GGUFTensorInfos, + // which can be nested. + // + // Branch nodes store in type pointer. + GGUFLayerTensorInfos `json:"items,omitempty"` + } +) + +// Layers converts the GGUFTensorInfos to GGUFLayerTensorInfos. +func (gf *GGUFFile) Layers(ignores ...string) GGUFLayerTensorInfos { + return gf.TensorInfos.Layers(ignores...) +} + +func (kv GGUFMetadataKV) ValueUint8() uint8 { + if kv.ValueType != GGUFMetadataValueTypeUint8 { + panic(fmt.Errorf("key %q try to get type Uint8 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[uint8](kv.Value) +} + +func (kv GGUFMetadataKV) ValueInt8() int8 { + if kv.ValueType != GGUFMetadataValueTypeInt8 { + panic(fmt.Errorf("key %q try to get type Int8 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[int8](kv.Value) +} + +func (kv GGUFMetadataKV) ValueUint16() uint16 { + if kv.ValueType != GGUFMetadataValueTypeUint16 { + panic(fmt.Errorf("key %q try to get type Uint16 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[uint16](kv.Value) +} + +func (kv GGUFMetadataKV) ValueInt16() int16 { + if kv.ValueType != GGUFMetadataValueTypeInt16 { + panic(fmt.Errorf("key %q try to get type Int16 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[int16](kv.Value) +} + +func (kv GGUFMetadataKV) ValueUint32() uint32 { + if kv.ValueType != GGUFMetadataValueTypeUint32 { + panic(fmt.Errorf("key %q try to get type Uint32 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[uint32](kv.Value) +} + +func (kv GGUFMetadataKV) ValueInt32() int32 { + if kv.ValueType != GGUFMetadataValueTypeInt32 { + panic(fmt.Errorf("key %q try to get type Int32 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[int32](kv.Value) +} + +func (kv GGUFMetadataKV) ValueFloat32() float32 { + if kv.ValueType != GGUFMetadataValueTypeFloat32 { + panic(fmt.Errorf("key %q try to get type Float32 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[float32](kv.Value) +} + +func (kv GGUFMetadataKV) ValueBool() bool { + if kv.ValueType != GGUFMetadataValueTypeBool { + panic(fmt.Errorf("key %q try to get type Bool but type %v", kv.Key, kv.ValueType)) + } + return anyx.Bool(kv.Value) +} + +func (kv GGUFMetadataKV) ValueString() string { + if kv.ValueType != GGUFMetadataValueTypeString { + panic(fmt.Errorf("key %q try to get type String but type %v", kv.Key, kv.ValueType)) + } + return anyx.String(kv.Value) +} + +func (kv GGUFMetadataKV) ValueArray() GGUFMetadataKVArrayValue { + if kv.ValueType != GGUFMetadataValueTypeArray { + panic(fmt.Errorf("key %q try to get type Array but type %v", kv.Key, kv.ValueType)) + } + switch t := kv.Value.(type) { + case GGUFMetadataKVArrayValue: + return t + case map[string]any: + return GGUFMetadataKVArrayValue{ + Type: anyx.Number[GGUFMetadataValueType](t["type"]), + Len: anyx.Number[uint64](t["len"]), + Array: func() []any { + if vv, ok := t["array"].([]any); ok { + return vv + } + return nil + }(), + StartOffset: anyx.Number[int64](t["startOffset"]), + Size: anyx.Number[int64](t["size"]), + } + default: + panic(fmt.Errorf("key %q try to get type Array but type %T", kv.Key, kv.Value)) + } +} + +func (kv GGUFMetadataKV) ValueUint64() uint64 { + if kv.ValueType != GGUFMetadataValueTypeUint64 { + panic(fmt.Errorf("key %q try to get type Uint64 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[uint64](kv.Value) +} + +func (kv GGUFMetadataKV) ValueInt64() int64 { + if kv.ValueType != GGUFMetadataValueTypeInt64 { + panic(fmt.Errorf("key %q try to get type Int64 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[int64](kv.Value) +} + +func (kv GGUFMetadataKV) ValueFloat64() float64 { + if kv.ValueType != GGUFMetadataValueTypeFloat64 { + panic(fmt.Errorf("key %q try to get type Float64 but type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[float64](kv.Value) +} + +// ValueNumeric returns the numeric values of the GGUFMetadataKV, +// and panics if the value type is not numeric. +// +// ValueNumeric is a generic function, and the type T must be constraints.Integer or constraints.Float. +// +// Compare to the GGUFMetadataKV's Value* functions, +// ValueNumeric will cast the original value to the target type. +func ValueNumeric[T constraints.Integer | constraints.Float](kv GGUFMetadataKV) T { + switch kv.ValueType { + case GGUFMetadataValueTypeUint8: + case GGUFMetadataValueTypeInt8: + case GGUFMetadataValueTypeUint16: + case GGUFMetadataValueTypeInt16: + case GGUFMetadataValueTypeUint32: + case GGUFMetadataValueTypeInt32: + case GGUFMetadataValueTypeFloat32: + case GGUFMetadataValueTypeUint64: + case GGUFMetadataValueTypeInt64: + case GGUFMetadataValueTypeFloat64: + default: + panic(fmt.Errorf("key %q try to get type Numeric but got type %v", kv.Key, kv.ValueType)) + } + return anyx.Number[T](kv.Value) +} + +func (av GGUFMetadataKVArrayValue) ValuesUint8() []uint8 { + if av.Type != GGUFMetadataValueTypeUint8 { + panic(fmt.Errorf("try to get type Uint8 but got type %v", av.Type)) + } + v := make([]uint8, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[uint8](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesInt8() []int8 { + if av.Type != GGUFMetadataValueTypeInt8 { + panic(fmt.Errorf("try to get type Int8 but got type %v", av.Type)) + } + v := make([]int8, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[int8](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesUint16() []uint16 { + if av.Type != GGUFMetadataValueTypeUint16 { + panic(fmt.Errorf("try to get type Uint16 but got type %v", av.Type)) + } + v := make([]uint16, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[uint16](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesInt16() []int16 { + if av.Type != GGUFMetadataValueTypeInt16 { + panic(fmt.Errorf("try to get type Int16 but got type %v", av.Type)) + } + v := make([]int16, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[int16](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesUint32() []uint32 { + if av.Type != GGUFMetadataValueTypeUint32 { + panic(fmt.Errorf("try to get type Uint8 but got type %v", av.Type)) + } + v := make([]uint32, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[uint32](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesInt32() []int32 { + if av.Type != GGUFMetadataValueTypeInt32 { + panic(fmt.Errorf("try to get type Int32 but got type %v", av.Type)) + } + v := make([]int32, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[int32](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesFloat32() []float32 { + if av.Type != GGUFMetadataValueTypeFloat32 { + panic(fmt.Errorf("try to get type Float32 but got type %v", av.Type)) + } + v := make([]float32, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[float32](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesBool() []bool { + if av.Type != GGUFMetadataValueTypeBool { + panic(fmt.Errorf("try to get type Bool but got type %v", av.Type)) + } + v := make([]bool, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Bool(av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesString() []string { + if av.Type != GGUFMetadataValueTypeString { + panic(fmt.Errorf("try to get type String but got type %v", av.Type)) + } + v := make([]string, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.String(av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesArray() []GGUFMetadataKVArrayValue { + if av.Type != GGUFMetadataValueTypeArray { + panic(fmt.Errorf("try to get type Array but got type %v", av.Type)) + } + v := make([]GGUFMetadataKVArrayValue, av.Len) + for i := uint64(0); i < av.Len; i++ { + switch t := av.Array[i].(type) { + case GGUFMetadataKVArrayValue: + v[i] = t + case map[string]any: + v[i] = GGUFMetadataKVArrayValue{ + Type: anyx.Number[GGUFMetadataValueType](t["type"]), + Len: anyx.Number[uint64](t["len"]), + Array: func() []any { + if vv, ok := t["array"].([]any); ok { + return vv + } + return nil + }(), + StartOffset: anyx.Number[int64](t["startOffset"]), + Size: anyx.Number[int64](t["size"]), + } + default: + panic(fmt.Errorf("try to get type Array but got type %T", av.Array[i])) + } + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesUint64() []uint64 { + if av.Type != GGUFMetadataValueTypeUint64 { + panic(fmt.Errorf("try to get type Uint16 but got type %v", av.Type)) + } + v := make([]uint64, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[uint64](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesInt64() []int64 { + if av.Type != GGUFMetadataValueTypeInt64 { + panic(fmt.Errorf("try to get type Int64 but got type %v", av.Type)) + } + v := make([]int64, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[int64](av.Array[i]) + } + return v +} + +func (av GGUFMetadataKVArrayValue) ValuesFloat64() []float64 { + if av.Type != GGUFMetadataValueTypeFloat64 { + panic(fmt.Errorf("try to get type Float64 but got type %v", av.Type)) + } + v := make([]float64, av.Len) + for i := uint64(0); i < av.Len; i++ { + v[i] = anyx.Number[float64](av.Array[i]) + } + return v +} + +// ValuesNumeric returns the numeric values of the GGUFMetadataKVArrayValue, +// and panics if the value type is not numeric. +// +// ValuesNumeric is a generic function, and the type T must be constraints.Integer or constraints.Float. +// +// Compare to the GGUFMetadataKVArrayValue's Value* functions, +// ValuesNumeric will cast the original value to the target type. +func ValuesNumeric[T constraints.Integer | constraints.Float](av GGUFMetadataKVArrayValue) []T { + v := make([]T, av.Len) + for i := uint64(0); i < av.Len; i++ { + switch av.Type { + case GGUFMetadataValueTypeUint8: + case GGUFMetadataValueTypeInt8: + case GGUFMetadataValueTypeUint16: + case GGUFMetadataValueTypeInt16: + case GGUFMetadataValueTypeUint32: + case GGUFMetadataValueTypeInt32: + case GGUFMetadataValueTypeFloat32: + case GGUFMetadataValueTypeUint64: + case GGUFMetadataValueTypeInt64: + case GGUFMetadataValueTypeFloat64: + default: + panic(fmt.Errorf("try to get type Numeric but got type %v", av.Type)) + } + if av.Array != nil { + v[i] = anyx.Number[T](av.Array[i]) + } + } + return v +} + +// Get returns the GGUFMetadataKV with the given key, +// and true if found, and false otherwise. +func (kvs GGUFMetadataKVs) Get(key string) (value GGUFMetadataKV, found bool) { + for i := range kvs { + if kvs[i].Key == key { + return kvs[i], true + } + } + return GGUFMetadataKV{}, false +} + +// Search returns a list of GGUFMetadataKV with the keys that match the given regex. +func (kvs GGUFMetadataKVs) Search(keyRegex *regexp.Regexp) (values []GGUFMetadataKV) { + for i := range kvs { + if keyRegex.MatchString(kvs[i].Key) { + values = append(values, kvs[i]) + } + } + return values +} + +// Index returns a map value to the GGUFMetadataKVs with the given keys, +// and the number of keys found. +func (kvs GGUFMetadataKVs) Index(keys []string) (values map[string]GGUFMetadataKV, found int) { + ks := make(map[string]struct{}, len(keys)) + for i := range keys { + ks[keys[i]] = struct{}{} + } + values = make(map[string]GGUFMetadataKV) + for i := range kvs { + if _, ok := ks[kvs[i].Key]; ok { + values[kvs[i].Key] = kvs[i] + found++ + } + if found == len(ks) { + break + } + } + return values, found +} + +// Get returns the GGUFTensorInfo with the given name, +// and true if found, and false otherwise. +func (ti GGUFTensorInfo) Get(name string) (info GGUFTensorInfo, found bool) { + if ti.Name == name { + return ti, true + } + return GGUFTensorInfo{}, false +} + +// GetFileType returns the GGUFFileType. +func (ti GGUFTensorInfo) GetFileType() GGUFFileType { + return GetFileType(map[GGMLType]int{ti.Type: 1}) +} + +// Match returns true if the name of the GGUFTensorInfo matches the given regex. +func (ti GGUFTensorInfo) Match(nameRegex *regexp.Regexp) bool { + return nameRegex.MatchString(ti.Name) +} + +// Search returns a list of GGUFTensorInfo with the names that match the given regex. +func (ti GGUFTensorInfo) Search(nameRegex *regexp.Regexp) (infos []GGUFTensorInfo) { + if nameRegex.MatchString(ti.Name) { + return []GGUFTensorInfo{ti} + } + return nil +} + +// Index returns a map value to the GGUFTensorInfo with the given names, +// and the number of names found. +func (ti GGUFTensorInfo) Index(names []string) (infos map[string]GGUFTensorInfo, found int) { + if len(names) == 0 { + return nil, 0 + } + if names[0] == ti.Name { + return map[string]GGUFTensorInfo{ti.Name: ti}, 1 + } + return nil, 0 +} + +// Elements returns the number of elements of the GGUFTensorInfo, +// which is inspired by +// https://github.com/ggerganov/ggml/blob/a10a8b880c059b3b29356eb9a9f8df72f03cdb6a/src/ggml.c#L2597-L2601. +func (ti GGUFTensorInfo) Elements() uint64 { + if ti.NDimensions == 0 { + return 0 + } + + ret := uint64(1) + for i := uint32(0); i < ti.NDimensions; i++ { + ret *= ti.Dimensions[i] + } + return ret +} + +// Bytes returns the number of bytes of the GGUFTensorInfo, +// which is inspired by +// https://github.com/ggerganov/ggml/blob/a10a8b880c059b3b29356eb9a9f8df72f03cdb6a/src/ggml.c#L2609-L2626. +func (ti GGUFTensorInfo) Bytes() uint64 { + if ti.NDimensions == 0 { + return 0 + } + + tt, ok := ti.Type.Trait() + if !ok { + panic(fmt.Errorf("invalid type: %v", ti.Type)) + } + + // https://github.com/ggerganov/ggml/blob/a10a8b880c059b3b29356eb9a9f8df72f03cdb6a/src/ggml.c#L3210-L3214 + nb := make([]uint64, 0, ti.NDimensions) + { + nb = append(nb, tt.TypeSize) + nb = append(nb, nb[0]*(ti.Dimensions[0]/tt.BlockSize)) + for i := uint32(2); i < ti.NDimensions; i++ { + nb = append(nb, nb[i-1]*ti.Dimensions[i-1]) + } + } + + var ret uint64 + if tt.BlockSize == 1 { + ret = tt.TypeSize + for i := uint32(0); i < ti.NDimensions; i++ { + ret += (ti.Dimensions[i] - 1) * nb[i] + } + return ret + } + + ret = ti.Dimensions[0] * nb[0] / tt.BlockSize + for i := uint32(1); i < ti.NDimensions; i++ { + ret += (ti.Dimensions[i] - 1) * nb[i] + } + return ret +} + +// Count returns the number of GGUF tensors of the GGUFTensorInfo, +// which is always 1. +func (ti GGUFTensorInfo) Count() uint64 { + return 1 +} + +// Get returns the GGUFTensorInfo with the given name, +// and true if found, and false otherwise. +func (tis GGUFTensorInfos) Get(name string) (info GGUFTensorInfo, found bool) { + for i := range tis { + if tis[i].Name == name { + return tis[i], true + } + } + return GGUFTensorInfo{}, false +} + +// GetFileType returns the GGUFFileType represented the mostly GGMLType of the GGUFTensorInfos. +func (tis GGUFTensorInfos) GetFileType() GGUFFileType { + if len(tis) == 0 { + return _GGUFFileTypeCount + } + + cm := make(map[GGMLType]int) + for i := range tis { + cm[tis[i].Type]++ + } + + return GetFileType(cm) +} + +// Match returns true if a tensor of GGUFTensorInfos matches the given regex. +func (tis GGUFTensorInfos) Match(nameRegex *regexp.Regexp) bool { + for i := range tis { + if nameRegex.MatchString(tis[i].Name) { + return true + } + } + return false +} + +// Search returns a list of GGUFTensorInfo with the names that match the given regex. +func (tis GGUFTensorInfos) Search(nameRegex *regexp.Regexp) (infos []GGUFTensorInfo) { + for i := range tis { + if nameRegex.MatchString(tis[i].Name) { + infos = append(infos, tis[i]) + } + } + return infos +} + +// Index returns a map value to the GGUFTensorInfos with the given names, +// and the number of names found. +func (tis GGUFTensorInfos) Index(names []string) (infos map[string]GGUFTensorInfo, found int) { + ns := make(map[string]struct{}, len(names)) + for i := range names { + ns[names[i]] = struct{}{} + } + infos = make(map[string]GGUFTensorInfo) + for i := range tis { + if _, ok := ns[tis[i].Name]; ok { + infos[tis[i].Name] = tis[i] + found++ + } + if found == len(ns) { + break + } + } + return infos, found +} + +// Elements returns the number of elements of the GGUFTensorInfos. +func (tis GGUFTensorInfos) Elements() uint64 { + var ret uint64 + for i := range tis { + ret += tis[i].Elements() + } + return ret +} + +// Bytes returns the number of bytes of the GGUFTensorInfos. +func (tis GGUFTensorInfos) Bytes() uint64 { + var ret uint64 + for i := range tis { + ret += tis[i].Bytes() + } + return ret +} + +// Count returns the number of GGUF tensors of the GGUFTensorInfos. +func (tis GGUFTensorInfos) Count() uint64 { + return uint64(len(tis)) +} + +// Layers converts the GGUFTensorInfos to GGUFLayerTensorInfos. +func (tis GGUFTensorInfos) Layers(ignores ...string) GGUFLayerTensorInfos { + if len(tis) == 0 { + return nil + } + + ls := tis.layers() + if len(ignores) != 0 { + _, ls, _ = ls.Cut(ignores) + return ls + } + return ls +} + +var numberRegex = regexp.MustCompile(`^\d+$`) + +func (tis GGUFTensorInfos) layers() GGUFLayerTensorInfos { + var ret GGUFLayerTensorInfos + + pm := make(map[string]any) + for i := range tis { + ps := strings.Split(tis[i].Name, ".") + if len(ps) < 2 { + ret = append(ret, tis[i]) + continue + } + switch { + default: + ret = append(ret, tis[i]) + case ps[0] == "blk" || ps[0] == "block": + // LLaMACpp. + p := strings.Join([]string{ps[0], ps[1]}, ".") + if _, ok := pm[p]; !ok { + l := &GGUFNamedTensorInfos{Name: p} + pm[p] = l + ret = append(ret, l) + } + l := pm[p].(*GGUFNamedTensorInfos) + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, tis[i]) + case (ps[0] == "v" || ps[0] == "t") && ps[1] == "blk": + // LLaMACpp CLIP. + p := ps[0] + if _, ok := pm[p]; !ok { + l := &GGUFNamedTensorInfos{Name: p} + pm[p] = l + ret = append(ret, l) + } + l := pm[p].(*GGUFNamedTensorInfos) + if len(ps) < 3 { + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, tis[i]) + continue + } + p = strings.Join([]string{ps[0], ps[1], ps[2]}, ".") + if _, ok := pm[p]; !ok { + xl := &GGUFNamedTensorInfos{Name: p} + pm[p] = xl + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, xl) + } + xl := pm[p].(*GGUFNamedTensorInfos) + xl.GGUFLayerTensorInfos = append(xl.GGUFLayerTensorInfos, tis[i]) + case ((ps[0] == "dec" || ps[0] == "enc") && ps[1] == "blk") || + ((ps[0] == "decoder" || ps[0] == "encoder") && ps[1] == "block"): + // BERT. + p := ps[0] + if _, ok := pm[p]; !ok { + l := &GGUFNamedTensorInfos{Name: p} + pm[p] = l + ret = append(ret, l) + } + l := pm[p].(*GGUFNamedTensorInfos) + if len(ps) < 3 { + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, tis[i]) + continue + } + p = strings.Join([]string{ps[0], ps[1], ps[2]}, ".") + if _, ok := pm[p]; !ok { + xl := &GGUFNamedTensorInfos{Name: p} + pm[p] = xl + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, xl) + } + xl := pm[p].(*GGUFNamedTensorInfos) + xl.GGUFLayerTensorInfos = append(xl.GGUFLayerTensorInfos, tis[i]) + case ps[0] == "first_stage_model": + // StableDiffusionCpp Autoencoder. + p := strings.Join([]string{ps[0], ps[1]}, ".") + if _, ok := pm[p]; !ok { + l := &GGUFNamedTensorInfos{Name: p} + pm[p] = l + ret = append(ret, l) + } + l := pm[p].(*GGUFNamedTensorInfos) + if len(ps) < 3 { + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, tis[i]) + continue + } + p = strings.Join([]string{ps[0], ps[1], ps[2]}, ".") + if _, ok := pm[p]; !ok { + xl := &GGUFNamedTensorInfos{Name: p} + pm[p] = xl + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, xl) + } + xl := pm[p].(*GGUFNamedTensorInfos) + xl.GGUFLayerTensorInfos = append(xl.GGUFLayerTensorInfos, tis[i]) + case ps[0] == "cond_stage_model": + // StableDiffusionCpp Conditioner. + if len(ps) < 3 { + ret = append(ret, tis[i]) + continue + } + p := strings.Join([]string{ps[0], ps[1], ps[2]}, ".") + if !numberRegex.MatchString(ps[1]) { + p = strings.Join([]string{ps[0], ps[1]}, ".") + } + if _, ok := pm[p]; !ok { + l := &GGUFNamedTensorInfos{Name: p} + pm[p] = l + ret = append(ret, l) + } + l := pm[p].(*GGUFNamedTensorInfos) + if len(ps) < 4 { + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, tis[i]) + continue + } + p = strings.Join([]string{ps[0], ps[1], ps[2], ps[3]}, ".") + if !numberRegex.MatchString(ps[1]) { + p = strings.Join([]string{ps[0], ps[1], ps[2]}, ".") + } + if _, ok := pm[p]; !ok { + xl := &GGUFNamedTensorInfos{Name: p} + pm[p] = xl + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, xl) + } + xl := pm[p].(*GGUFNamedTensorInfos) + xl.GGUFLayerTensorInfos = append(xl.GGUFLayerTensorInfos, tis[i]) + case ps[0] == "model" && ps[1] == "diffusion_model": // nolint: goconst + // StableDiffusionCpp. + p := "model.diffusion_model" + if _, ok := pm[p]; !ok { + l := &GGUFNamedTensorInfos{Name: p} + pm[p] = l + ret = append(ret, l) + } + l := pm[p].(*GGUFNamedTensorInfos) + if len(ps) < 3 { + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, tis[i]) + continue + } + p = strings.Join([]string{"model.diffusion_model", ps[2]}, ".") + if _, ok := pm[p]; !ok { + xl := &GGUFNamedTensorInfos{Name: p} + pm[p] = xl + l.GGUFLayerTensorInfos = append(l.GGUFLayerTensorInfos, xl) + } + xl := pm[p].(*GGUFNamedTensorInfos) + xl.GGUFLayerTensorInfos = append(xl.GGUFLayerTensorInfos, tis[i]) + } + } + return ret +} + +// Get returns the IGGUFTensorInfos with the given name, +// and true if found, and false otherwise. +func (ltis GGUFLayerTensorInfos) Get(name string) (info GGUFTensorInfo, found bool) { + for i := range ltis { + switch v := ltis[i].(type) { + case GGUFTensorInfo: + if v.Name == name { + return v, true + } + case *GGUFNamedTensorInfos: + info, found = v.GGUFLayerTensorInfos.Get(name) + if found { + return info, true + } + } + } + return GGUFTensorInfo{}, false +} + +// GetFileType returns the GGUFFileType represented the mostly GGMLType of the GGUFLayerTensorInfos. +func (ltis GGUFLayerTensorInfos) GetFileType() GGUFFileType { + if len(ltis) == 0 { + return _GGUFFileTypeCount + } + + cm := make(map[GGMLType]int) + for i := range ltis { + switch v := ltis[i].(type) { + case GGUFTensorInfo: + cm[v.Type]++ + case *GGUFNamedTensorInfos: + cm[v.GetFileType().GGMLType()]++ + } + } + + return GetFileType(cm) +} + +// Match returns true if a tensor of GGUFLayerTensorInfos matches the given regex. +func (ltis GGUFLayerTensorInfos) Match(nameRegex *regexp.Regexp) bool { + for i := range ltis { + switch v := ltis[i].(type) { + case GGUFTensorInfo: + if nameRegex.MatchString(v.Name) { + return true + } + case *GGUFNamedTensorInfos: + if v.Match(nameRegex) { + return true + } + } + } + return false +} + +// Search returns a list of GGUFTensorInfo with the names that match the given regex. +func (ltis GGUFLayerTensorInfos) Search(nameRegex *regexp.Regexp) (infos []GGUFTensorInfo) { + for i := range ltis { + switch v := ltis[i].(type) { + case GGUFTensorInfo: + if nameRegex.MatchString(v.Name) { + infos = append(infos, v) + } + case *GGUFNamedTensorInfos: + infos = append(infos, v.Search(nameRegex)...) + } + } + return infos +} + +// Index returns a map value to the GGUFTensorInfos with the given names, +// and the number of names found. +func (ltis GGUFLayerTensorInfos) Index(names []string) (infos map[string]GGUFTensorInfo, found int) { + ns := make(map[string]struct{}, len(names)) + for i := range names { + ns[names[i]] = struct{}{} + } + infos = make(map[string]GGUFTensorInfo) + for i := range ltis { + switch v := ltis[i].(type) { + case GGUFTensorInfo: + if _, ok := ns[v.Name]; ok { + infos[v.Name] = v + found++ + } + case *GGUFNamedTensorInfos: + inf, _ := v.Index(names) + for k := range inf { + infos[k] = inf[k] + found++ + } + } + if found == len(ns) { + break + } + } + return infos, found +} + +// Elements returns the number of elements of the GGUFLayerTensorInfos. +func (ltis GGUFLayerTensorInfos) Elements() uint64 { + var ret uint64 + for i := range ltis { + ret += ltis[i].Elements() + } + return ret +} + +// Bytes returns the number of bytes of the GGUFLayerTensorInfos. +func (ltis GGUFLayerTensorInfos) Bytes() uint64 { + var ret uint64 + for i := range ltis { + ret += ltis[i].Bytes() + } + return ret +} + +// Count returns the number of GGUF tensors of the GGUFLayerTensorInfos. +func (ltis GGUFLayerTensorInfos) Count() uint64 { + var ret uint64 + for i := range ltis { + ret += ltis[i].Count() + } + return ret +} + +// Cut splits the GGUFLayerTensorInfos into two parts, +// and returns the GGUFLayerTensorInfos with the names that match the given names at first, +// and the GGUFLayerTensorInfos without the names at second, +// and true if the GGUFLayerTensorInfos with the names are found, and false otherwise. +// +// The given names support glob pattern, for example, "a*" matches "a", "ab", "abc", and so on. +func (ltis GGUFLayerTensorInfos) Cut(names []string) (before, after GGUFLayerTensorInfos, found bool) { + prefixes := make(map[string]struct{}) + matches := make(map[string]struct{}) + for i := range names { + if strings.HasSuffix(names[i], "*") { + prefixes[strings.TrimSuffix(names[i], "*")] = struct{}{} + } else { + matches[names[i]] = struct{}{} + } + } + before = make(GGUFLayerTensorInfos, 0, len(names)) + after = make(GGUFLayerTensorInfos, 0, len(ltis)) + + for i := range ltis { + switch v := ltis[i].(type) { + case GGUFTensorInfo: + if len(matches) != 0 { + if _, ok := matches[v.Name]; ok { + before = append(before, v) + continue + } + } + if len(prefixes) != 0 { + var check bool + for prefix := range prefixes { + if strings.HasPrefix(v.Name, prefix) { + before = append(before, v) + check = true + break + } + } + if check { + continue + } + } + after = append(after, v) + case *GGUFNamedTensorInfos: + if len(matches) != 0 { + if _, ok := matches[v.Name]; ok { + before = append(before, v) + continue + } + } + if len(prefixes) != 0 { + var check bool + for prefix := range prefixes { + if strings.HasPrefix(v.Name, prefix) { + before = append(before, v) + check = true + break + } + } + if check { + continue + } + } + after = append(after, v) + } + } + return before, after, len(before) > 0 +} + +type _GGUFReader struct { + v GGUFVersion + o _GGUFReadOptions + f io.ReadSeeker + bo binary.ByteOrder +} + +func (rd _GGUFReader) ReadUint8() (v uint8, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read uint8: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadInt8() (v int8, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read int8: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadUint16() (v uint16, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read uint16: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadInt16() (v int16, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read int16: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadUint32() (v uint32, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read uint32: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadUint64FromUint32() (uint64, error) { + v, err := rd.ReadUint32() + return uint64(v), err +} + +func (rd _GGUFReader) ReadInt32() (v int32, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read int32: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadFloat32() (v float32, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read float32: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadBool() (v bool, err error) { + b, err := rd.ReadUint8() + if err != nil { + return false, fmt.Errorf("read bool: %w", err) + } + return b != 0, nil +} + +func (rd _GGUFReader) ReadString() (v string, err error) { + var l uint64 + if rd.v <= GGUFVersionV1 { + l, err = rd.ReadUint64FromUint32() + } else { + l, err = rd.ReadUint64() + } + if err != nil { + return "", fmt.Errorf("read string length: %w", err) + } + + b := bytex.GetBytes(l) + defer bytex.Put(b) + if _, err = rd.f.Read(b); err != nil { + return "", fmt.Errorf("read string: %w", err) + } + + return string(bytes.TrimSpace(b)), nil +} + +func (rd _GGUFReader) SkipReadingString() (err error) { + var l uint64 + if rd.v <= GGUFVersionV1 { + l, err = rd.ReadUint64FromUint32() + } else { + l, err = rd.ReadUint64() + } + if err != nil { + return fmt.Errorf("read string length: %w", err) + } + _, err = rd.f.Seek(int64(l), io.SeekCurrent) + if err != nil { + return fmt.Errorf("seek string: %w", err) + } + return nil +} + +func (rd _GGUFReader) ReadArray(key string) (v GGUFMetadataKVArrayValue, err error) { + v.StartOffset, err = rd.f.Seek(0, io.SeekCurrent) + if err != nil { + return v, fmt.Errorf("read array start: %w", err) + } + + if err = binary.Read(rd.f, rd.bo, &v.Type); err != nil { + return v, fmt.Errorf("read array item type: %w", err) + } + + if rd.v <= GGUFVersionV1 { + v.Len, err = rd.ReadUint64FromUint32() + } else { + v.Len, err = rd.ReadUint64() + } + if err != nil { + return v, fmt.Errorf("read array length: %w", err) + } + + itemStart, err := rd.f.Seek(0, io.SeekCurrent) + if err != nil { + return v, fmt.Errorf("seek array item start: %w", err) + } + + if !rd.o.SkipLargeMetadata || stringx.HasSuffixes(key, ".feed_forward_length", ".attention.head_count") { + v.Array = make([]any, v.Len) + for i := uint64(0); i < v.Len; i++ { + v.Array[i], err = rd.ReadValue(key, v.Type) + if err != nil { + return v, fmt.Errorf("read array item %d: %w", i, err) + } + } + + itemEnd, err := rd.f.Seek(0, io.SeekCurrent) + if err != nil { + return v, fmt.Errorf("seek array item end: %w", err) + } + v.Size = itemEnd - itemStart + + return v, nil + } + + switch v.Type { + case GGUFMetadataValueTypeUint8, GGUFMetadataValueTypeInt8, GGUFMetadataValueTypeBool: + _, err = rd.f.Seek(int64(v.Len), io.SeekCurrent) + case GGUFMetadataValueTypeUint16, GGUFMetadataValueTypeInt16: + _, err = rd.f.Seek(int64(v.Len)*2, io.SeekCurrent) + case GGUFMetadataValueTypeUint32, GGUFMetadataValueTypeInt32, GGUFMetadataValueTypeFloat32: + _, err = rd.f.Seek(int64(v.Len)*4, io.SeekCurrent) + case GGUFMetadataValueTypeUint64, GGUFMetadataValueTypeInt64, GGUFMetadataValueTypeFloat64: + _, err = rd.f.Seek(int64(v.Len)*8, io.SeekCurrent) + case GGUFMetadataValueTypeString: + for i := uint64(0); i < v.Len; i++ { + if err = rd.SkipReadingString(); err != nil { + return v, fmt.Errorf("seek array[string] %d: %w", i, err) + } + } + default: + // Should not happen. + panic(fmt.Errorf("invalid type: %v", v.Type)) + } + if err != nil { + return v, fmt.Errorf("seek array end: %w", err) + } + + itemEnd, err := rd.f.Seek(0, io.SeekCurrent) + if err != nil { + return v, fmt.Errorf("seek array item end: %w", err) + } + v.Size = itemEnd - itemStart + + return v, nil +} + +func (rd _GGUFReader) ReadUint64() (v uint64, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read uint64: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadInt64() (v int64, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read int64: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadFloat64() (v float64, err error) { + err = binary.Read(rd.f, rd.bo, &v) + if err != nil { + return 0, fmt.Errorf("read float64: %w", err) + } + return v, nil +} + +func (rd _GGUFReader) ReadValue(vk string, vt GGUFMetadataValueType) (v any, err error) { + if vt >= _GGUFMetadataValueTypeCount { + return nil, fmt.Errorf("invalid type: %v", vt) + } + + switch vt { + case GGUFMetadataValueTypeUint8: + v, err = rd.ReadUint8() + case GGUFMetadataValueTypeInt8: + v, err = rd.ReadInt8() + case GGUFMetadataValueTypeUint16: + v, err = rd.ReadUint16() + case GGUFMetadataValueTypeInt16: + v, err = rd.ReadInt16() + case GGUFMetadataValueTypeUint32: + v, err = rd.ReadUint32() + case GGUFMetadataValueTypeInt32: + v, err = rd.ReadInt32() + case GGUFMetadataValueTypeFloat32: + v, err = rd.ReadFloat32() + case GGUFMetadataValueTypeBool: + v, err = rd.ReadBool() + case GGUFMetadataValueTypeString: + v, err = rd.ReadString() + case GGUFMetadataValueTypeArray: + v, err = rd.ReadArray(vk) + case GGUFMetadataValueTypeUint64: + v, err = rd.ReadUint64() + case GGUFMetadataValueTypeInt64: + v, err = rd.ReadInt64() + case GGUFMetadataValueTypeFloat64: + v, err = rd.ReadFloat64() + default: + // Should not happen. + panic(fmt.Errorf("invalid type: %v", vt)) + } + if err != nil { + return nil, err + } + return v, nil +} + +type _GGUFMetadataReader struct { + _GGUFReader +} + +func (rd _GGUFMetadataReader) Read() (kv GGUFMetadataKV, err error) { + kv.Key, err = rd.ReadString() + if err != nil { + return kv, fmt.Errorf("read key: %w", err) + } + + { + vt, err := rd.ReadUint32() + if err != nil { + return kv, fmt.Errorf("read value type: %w", err) + } + kv.ValueType = GGUFMetadataValueType(vt) + if kv.ValueType >= _GGUFMetadataValueTypeCount { + return kv, fmt.Errorf("invalid value type: %v", kv.ValueType) + } + } + + kv.Value, err = rd.ReadValue(kv.Key, kv.ValueType) + if err != nil { + return kv, fmt.Errorf("read %s value: %w", kv.Key, err) + } + + return kv, nil +} + +type _GGUFTensorInfoReader struct { + _GGUFReader +} + +func (rd _GGUFTensorInfoReader) Read() (ti GGUFTensorInfo, err error) { + ti.StartOffset, err = rd.f.Seek(0, io.SeekCurrent) + if err != nil { + return ti, fmt.Errorf("seek tensor info start: %w", err) + } + + ti.Name, err = rd.ReadString() + if err != nil { + return ti, fmt.Errorf("read name: %w", err) + } + + ti.NDimensions, err = rd.ReadUint32() + if err != nil { + return ti, fmt.Errorf("read n dimensions: %w", err) + } + + ti.Dimensions = make([]uint64, ti.NDimensions) + for i := uint32(0); i < ti.NDimensions; i++ { + if rd.v <= GGUFVersionV1 { + ti.Dimensions[i], err = rd.ReadUint64FromUint32() + } else { + ti.Dimensions[i], err = rd.ReadUint64() + } + if err != nil { + return ti, fmt.Errorf("read dimension %d: %w", i, err) + } + } + + { + v, err := rd.ReadUint32() + if err != nil { + return ti, fmt.Errorf("read type: %w", err) + } + ti.Type = GGMLType(v) + if ti.Type >= _GGMLTypeCount { + return ti, fmt.Errorf("invalid type: %v", ti.Type) + } + } + + ti.Offset, err = rd.ReadUint64() + if err != nil { + return ti, fmt.Errorf("read offset: %w", err) + } + + return ti, nil +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_architecture.go b/vendor/github.com/gpustack/gguf-parser-go/file_architecture.go new file mode 100644 index 00000000..f545d0ee --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_architecture.go @@ -0,0 +1,818 @@ +package gguf_parser + +import ( + "regexp" + "strings" +) + +// Types for the architecture metadata of a GGUF file. +type ( + // GGUFArchitecture represents the architecture metadata of a GGUF file. + GGUFArchitecture struct { + /* Basic */ + + // Type describes the type of the file, + // default is "model". + Type string `json:"type"` + // Architecture describes what architecture this model implements. + // + // All lowercase ASCII. + Architecture string `json:"architecture"` + // MaximumContextLength(n_ctx_train) is the maximum context length of the model. + // + // For most architectures, this is the hard limit on the length of the input. + // Architectures, like RWKV, + // that are not reliant on transformer-style attention may be able to handle larger inputs, + // but this is not guaranteed. + MaximumContextLength uint64 `json:"maximumContextLength,omitempty"` + // EmbeddingLength(n_embd) is the length of the embedding layer. + EmbeddingLength uint64 `json:"embeddingLength,omitempty"` + // BlockCount(n_layer) is the number of blocks of attention and feed-forward layers, + // i.e. the bulk of the LLM. + // This does not include the input or embedding layers. + BlockCount uint64 `json:"blockCount,omitempty"` + // FeedForwardLength(n_ff) stores the length of each feed-forward layer. + FeedForwardLength []uint64 `json:"feedForwardLength,omitempty"` + // ExpertFeedForwardLength(expert_feed_forward_length) is the length of the feed-forward layer in the expert model. + ExpertFeedForwardLength uint64 `json:"expertFeedForwardLength,omitempty"` + // ExpertSharedFeedForwardLength(expert_shared_feed_forward_length) is the length of the shared feed-forward layer in the expert model. + ExpertSharedFeedForwardLength uint64 `json:"expertSharedFeedForwardLength,omitempty"` + // ExpertCount(n_expert) is the number of experts in MoE models. + ExpertCount uint32 `json:"expertCount,omitempty"` + // ExpertUsedCount(n_expert_used) is the number of experts used during each token evaluation in MoE models. + ExpertUsedCount uint32 `json:"expertUsedCount,omitempty"` + // AttentionHeadCount(n_head) is the number of attention heads. + AttentionHeadCount uint64 `json:"attentionHeadCount,omitempty"` + // AttentionHeadCountKV(n_head_kv) is the number of attention heads per group used in Grouped-Query-Attention. + // + // If not provided or equal to AttentionHeadCount, + // the model does not use Grouped-Query-Attention. + AttentionHeadCountKV uint64 `json:"attentionHeadCountKV,omitempty"` + // AttentionMaxALiBIBias is the maximum bias to use for ALiBI. + AttentionMaxALiBIBias float32 `json:"attentionMaxALiBIBias,omitempty"` + // AttentionClampKQV describes a value `C`, + // which is used to clamp the values of the `Q`, `K` and `V` tensors between `[-C, C]`. + AttentionClampKQV float32 `json:"attentionClampKQV,omitempty"` + // AttentionLayerNormEpsilon is the epsilon value used in the LayerNorm(Layer Normalization). + AttentionLayerNormEpsilon float32 `json:"attentionLayerNormEpsilon,omitempty"` + // AttentionLayerNormRMSEpsilon is the epsilon value used in the RMSNorm(root Mean Square Layer Normalization), + // which is a simplification of the original LayerNorm. + AttentionLayerNormRMSEpsilon float32 `json:"attentionLayerNormRMSEpsilon,omitempty"` + // AttentionKeyLength(n_embd_head_k) is the size of a key head. + // + // Defaults to `EmbeddingLength / AttentionHeadCount`. + AttentionKeyLength uint32 `json:"attentionKeyLength,omitempty"` + // AttentionValueLength(n_embd_head_v) is the size of a value head. + // + // Defaults to `EmbeddingLength / AttentionHeadCount`. + AttentionValueLength uint32 `json:"attentionValueLength,omitempty"` + // AttentionCausal is true if the attention is causal. + AttentionCausal bool `json:"attentionCausal,omitempty"` + // RoPEDimensionCount is the number of dimensions in the RoPE(Rotary Positional Encoding). + RoPEDimensionCount uint64 `json:"ropeDimensionCount,omitempty"` + // RoPEFrequencyBase is the base frequency of the RoPE. + RoPEFrequencyBase float32 `json:"ropeFrequencyBase,omitempty"` + // RoPEFrequencyScale is the frequency scale of the RoPE. + RoPEScalingType string `json:"ropeScalingType,omitempty"` + // RoPEScalingFactor is the scaling factor of the RoPE. + RoPEScalingFactor float32 `json:"ropeScalingFactor,omitempty"` + // RoPEScalingOriginalContextLength is the original context length of the RoPE scaling. + RoPEScalingOriginalContextLength uint64 `json:"ropeScalingOriginalContextLength,omitempty"` + // RoPEScalingFinetuned is true if the RoPE scaling is fine-tuned. + RoPEScalingFinetuned bool `json:"ropeScalingFinetuned,omitempty"` + // SSMConvolutionKernel is the size of the convolution kernel used in the SSM(Selective State Space Model). + SSMConvolutionKernel uint32 `json:"ssmConvolutionKernel,omitempty"` + // SSMInnerSize is the embedding size of the state in SSM. + SSMInnerSize uint32 `json:"ssmInnerSize,omitempty"` + // SSMStateSize is the size of the recurrent state in SSM. + SSMStateSize uint32 `json:"ssmStateSize,omitempty"` + // SSMTimeStepRank is the rank of the time steps in SSM. + SSMTimeStepRank uint32 `json:"ssmTimeStepRank,omitempty"` + // VocabularyLength is the size of the vocabulary. + // + // VocabularyLength is the same as the tokenizer's token size. + VocabularyLength uint64 `json:"vocabularyLength,omitempty"` + + /* Appendix */ + + // EmbeddingGGQA is the GQA of the embedding layer. + EmbeddingGQA uint64 `json:"embeddingGQA,omitempty"` + // EmbeddingKeyGQA is the number of key GQA in the embedding layer. + EmbeddingKeyGQA uint64 `json:"embeddingKeyGQA,omitempty"` + // EmbeddingValueGQA is the number of value GQA in the embedding layer. + EmbeddingValueGQA uint64 `json:"embeddingValueGQA,omitempty"` + + // ClipProjectorType is the type of the projector used in the clip model. + // + // Only used when Architecture is "clip". + ClipProjectorType string `json:"clipProjectorType,omitempty"` + // ClipHasLLaVAProjector indicates whether the clip model has LLaVA projector or not. + // + // Only used when Architecture is "clip". + ClipHasLLaVAProjector bool `json:"clipHasLLaVAProjector,omitempty"` + // ClipHasMiniCPMVProjector indicates whether the clip model has MiniCPMV projector or not. + // + // Only used when Architecture is "clip". + ClipHasMiniCPMVProjector bool `json:"clipHasMiniCPMVProject,omitempty"` + // ClipMiniCPMVVersion is the version of the MiniCPMV projector. + // + // Only used when Architecture is "clip" and ClipHasMiniCPMVProjector is true. + ClipMiniCPMVVersion int32 `json:"clipMiniCPMVVersion,omitempty"` + // ClipHasGLMProjector indicates whether the clip model has GLM projector or not. + // + // Only used when Architecture is "clip". + ClipHasGLMProjector bool `json:"clipHasGLMProjector,omitempty"` + // ClipHasQwen2VLMerger indicates whether the clip model has Qwen2VL merger or not. + // + // Only used when Architecture is "clip". + ClipHasQwen2VLMerger bool `json:"clipHasQwen2VLMerger,omitempty"` + // ClipHasTextEncoder indicates whether the clip model has text encoder or not. + // + // Only used when Architecture is "clip". + ClipHasTextEncoder bool `json:"clipHasTextEncoder,omitempty"` + // ClipHasVisionEncoder indicates whether the clip model has vision encoder or not. + // + // Only used when Architecture is "clip". + ClipHasVisionEncoder bool `json:"clipHasVisionEncoder,omitempty"` + // ClipVisionImageSize indicates the image size of vision encoder. + // + // Only used when Architecture is "clip" and ClipHasVisionEncoder is true. + ClipVisionImageSize uint32 `json:"clipVisionImageSize,omitempty"` + // ClipVisionPatchSize indicates the patch size of vision encoder. + // + // Only used when Architecture is "clip" and ClipHasVisionEncoder is true. + ClipVisionPatchSize uint32 `json:"clipVisionPatchSize,omitempty"` + // ClipVisionProjectionDim indicates the projection dimension of vision encoder. + // + // Only used when Architecture is "clip" and ClipHasVisionEncoder is true. + ClipVisionProjectionDim uint32 `json:"clipVisionProjectionDim,omitempty"` + // ClipVisionMMPatchMergeType indicates the merge type of the vision encoder. + // + // Only used when Architecture is "clip" and ClipHasVisionEncoder is true. + ClipVisionMMPatchMergeType string `json:"clipVisionMMPatchMergeType,omitempty"` + + // AdapterType is the type of the adapter. + // + // Only used when Architecture is "adapter". + AdapterType string `json:"adapterType,omitempty"` + // AdapterLoRAAlpha is the alpha value of the LoRA adapter. + // + // Only used when AdapterType is "lora". + AdapterLoRAAlpha float32 `json:"adapterLoRAAlpha,omitempty"` + // AdapterControlVectorLayerCount is the number of layers in the control vector. + // + // Only used when Architecture is "control_vector". + AdapterControlVectorLayerCount uint32 `json:"adapterControlVectorLayerCount,omitempty"` + + // DiffusionArchitecture is the actual architecture of the diffusion model. + // + // Only used when Architecture is "diffusion". + DiffusionArchitecture string `json:"diffusionArchitecture,omitempty"` + // DiffusionTransformer indicates whether the diffusion model is a diffusion transformer or not. + // + DiffusionTransformer bool `json:"diffusionTransformer,omitempty"` + // DiffusionConditioners is the list of diffusion conditioners. + // + // Only used when Architecture is "diffusion". + DiffusionConditioners GGUFArchitectureDiffusionConditioners `json:"diffusionConditioners,omitempty"` + // DiffusionAutoencoder represents the autoencoder of the diffusion model. + // + // Only used when Architecture is "diffusion". + DiffusionAutoencoder *GGUFArchitectureDiffusionAutoencoder `json:"diffusionAutoencoder,omitempty"` + } + + // GGUFArchitectureDiffusionConditioners is the list of GGUFArchitectureDiffusionConditioner. + GGUFArchitectureDiffusionConditioners []GGUFArchitectureDiffusionConditioner + + // GGUFArchitectureDiffusionConditioner represents the conditioner metadata of the diffusion architecture. + GGUFArchitectureDiffusionConditioner struct { + // Architecture is the architecture of the diffusion conditioner. + Architecture string `json:"architecture"` + + // FileType describes the type of the majority of the tensors in the GGUF file. + FileType GGUFFileType `json:"fileType"` + } + + // GGUFArchitectureDiffusionAutoencoder represents the autoencoder metadata of the diffusion architecture. + GGUFArchitectureDiffusionAutoencoder struct { + // Architecture is the architecture of the diffusion autoencoder. + // + // Currently, only "VAE" is supported. + Architecture string `json:"architecture"` + + // FileType describes the type of the majority of the tensors in the GGUF file. + FileType GGUFFileType `json:"fileType"` + } +) + +// DiffusionHasConditioners returns true if the diffusion model has conditioners. +func (ga GGUFArchitecture) DiffusionHasConditioners() bool { + return len(ga.DiffusionConditioners) > 0 +} + +// DiffusionHasAutoencoder returns true if the diffusion model has an autoencoder. +func (ga GGUFArchitecture) DiffusionHasAutoencoder() bool { + return ga.DiffusionAutoencoder != nil && ga.DiffusionAutoencoder.Architecture != "" +} + +func (gacs GGUFArchitectureDiffusionConditioners) String() string { + var sb strings.Builder + for i, gac := range gacs { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(gac.String()) + } + return sb.String() +} + +func (gac GGUFArchitectureDiffusionConditioner) String() string { + return gac.Architecture + " (" + gac.FileType.String() + ")" +} + +func (gaa GGUFArchitectureDiffusionAutoencoder) String() string { + return gaa.Architecture + " (" + gaa.FileType.String() + ")" +} + +// Architecture returns the architecture metadata of the GGUF file. +func (gf *GGUFFile) Architecture() (ga GGUFArchitecture) { + if gf.TensorInfos.Match(regexp.MustCompile(`^model\.diffusion_model\..*`)) || + gf.TensorInfos.Match(regexp.MustCompile(`^double_blocks\..*`)) { + return gf.diffuserArchitecture() + } + + var ( + generalTypeKey = "general.type" + generalArchitectureKey = "general.architecture" + + controlVectorModelHintKey = "controlvector.model_hint" + ) + m, _ := gf.Header.MetadataKV.Index([]string{ + generalTypeKey, + generalArchitectureKey, + controlVectorModelHintKey, + }) + + typ, arch := "model", "llama" // nolint: goconst + { + if v, ok := m[generalTypeKey]; ok { + typ = v.ValueString() + } + if v, ok := m[generalArchitectureKey]; ok { + arch = v.ValueString() + } + } + + switch { + case arch == "clip": + return gf.clipArchitecture() + case arch == "controlvector": + arch = "llama" + if v, ok := m[controlVectorModelHintKey]; ok { + arch = v.ValueString() + } + return gf.adapterArchitecture(arch) + case typ == "adapter": + return gf.adapterArchitecture(arch) + } + return gf.transformerArchitecture(arch) +} + +func (gf *GGUFFile) diffuserArchitecture() (ga GGUFArchitecture) { + const ( + // Diffusion + + sdKey = "model.diffusion_model.output_blocks.11.1.transformer_blocks.0.attn2.to_v.weight" // SD 1.x/2.x + sdXlKey = "model.diffusion_model.output_blocks.5.1.transformer_blocks.1.attn1.to_v.weight" // SD XL + sdXlRefinerKey = "model.diffusion_model.output_blocks.8.1.transformer_blocks.1.attn1.to_v.weight" // SD XL Refiner + sd3Key = "model.diffusion_model.joint_blocks.23.x_block.attn.proj.weight" // SD 3.x + sdInPaintFeatureKey = "model.diffusion_model.input_blocks.0.0.weight" // SD in-paint feature + + fluxKey = "model.diffusion_model.double_blocks.0.txt_attn.proj.weight" // FLUX.1 + fluxKey2 = "double_blocks.0.txt_attn.proj.weight" + fluxFillFeatureKey = "model.diffusion_model.img_in.weight" // FLUX.1 Fill feature + fluxFillFeatureKey2 = "img_in.weight" + + // Conditioner + + openAiClipVitL14Key = "cond_stage_model.transformer.text_model.encoder.layers.11.self_attn.k_proj.weight" // OpenAI CLIP ViT-L/14 + openClipVitH14Key = "cond_stage_model.transformer.text_model.encoder.layers.22.self_attn.k_proj.weight" // OpenCLIP ViT-H/14 + openClipVitG14Key = "cond_stage_model.1.transformer.text_model.encoder.layers.31.self_attn.k_proj.weight" // OpenCLIP ViT-G/14 + t5xxlKey = "cond_stage_model.1.transformer.encoder.block.23.layer.0.SelfAttention.k.weight" // Google T5-xxl + t5xxlKey2 = "cond_stage_model.2.transformer.encoder.block.23.layer.0.SelfAttention.k.weight" + ) + + tis, _ := gf.TensorInfos.Index([]string{ + sdKey, + sdXlKey, + sdXlRefinerKey, + sd3Key, + sdInPaintFeatureKey, + + fluxKey, + fluxKey2, + fluxFillFeatureKey, + fluxFillFeatureKey2, + + openAiClipVitL14Key, + openClipVitH14Key, + openClipVitG14Key, + t5xxlKey, + t5xxlKey2, + }) + + ga.Type = "model" + ga.Architecture = "diffusion" + + if ti, ok := tis[sdKey]; ok { + ga.DiffusionArchitecture = "Stable Diffusion 1.x" + if ti.Dimensions[0] == 1024 { + ga.DiffusionArchitecture = "Stable Diffusion 2.x" + } + if ti, ok := tis[sdInPaintFeatureKey]; ok && ti.Dimensions[2] == 9 { + ga.DiffusionArchitecture += " InPaint" + } + } else if _, ok := tis[sdXlKey]; ok { + ga.DiffusionArchitecture = "Stable Diffusion XL" + if _, ok = tis[sdXlRefinerKey]; ok { + ga.DiffusionArchitecture = "Stable Diffusion XL Refiner" + } + if ti, ok := tis[sdInPaintFeatureKey]; ok && ti.Dimensions[2] == 9 { + ga.DiffusionArchitecture += " InPaint" + } + } else if _, ok := tis[sd3Key]; ok { + ga.DiffusionArchitecture = "Stable Diffusion 3.x" + ga.DiffusionTransformer = true + } + if _, ok := tis[fluxKey]; ok { + ga.DiffusionArchitecture = "FLUX.1" + ga.DiffusionTransformer = true + if ti, ok := tis[fluxFillFeatureKey]; ok && ti.Dimensions[0] == 384 { + ga.DiffusionArchitecture += " Fill" + } + } else if _, ok := tis[fluxKey2]; ok { + ga.DiffusionArchitecture = "FLUX.1" + ga.DiffusionTransformer = true + if ti, ok := tis[fluxFillFeatureKey2]; ok && ti.Dimensions[0] == 384 { + ga.DiffusionArchitecture += " Fill" + } + } + + if ti, ok := tis[openAiClipVitL14Key]; ok { + cond := GGUFArchitectureDiffusionConditioner{ + Architecture: "OpenAI CLIP ViT-L/14", + FileType: ti.GetFileType(), + } + if ti, ok = tis[openClipVitH14Key]; ok { + cond = GGUFArchitectureDiffusionConditioner{ + Architecture: "OpenCLIP ViT-H/14", + FileType: ti.GetFileType(), + } + } + ga.DiffusionConditioners = append(ga.DiffusionConditioners, cond) + } + if ti, ok := tis[openClipVitG14Key]; ok { + ga.DiffusionConditioners = append(ga.DiffusionConditioners, GGUFArchitectureDiffusionConditioner{ + Architecture: "OpenCLIP ViT-G/14", + FileType: ti.GetFileType(), + }) + } + if ti, ok := tis[t5xxlKey]; ok { + ga.DiffusionConditioners = append(ga.DiffusionConditioners, GGUFArchitectureDiffusionConditioner{ + Architecture: "Google T5-xxl", + FileType: ti.GetFileType(), + }) + } else if ti, ok = tis[t5xxlKey2]; ok { + ga.DiffusionConditioners = append(ga.DiffusionConditioners, GGUFArchitectureDiffusionConditioner{ + Architecture: "Google T5-xxl", + FileType: ti.GetFileType(), + }) + } + + if tis := gf.TensorInfos.Search(regexp.MustCompile(`^first_stage_model\..*`)); len(tis) != 0 { + ga.DiffusionAutoencoder = &GGUFArchitectureDiffusionAutoencoder{ + Architecture: ga.DiffusionArchitecture + " VAE", + FileType: GGUFTensorInfos(tis).GetFileType(), + } + } + + return ga +} + +func (gf *GGUFFile) clipArchitecture() (ga GGUFArchitecture) { + const ( + projectorTypeKey = "clip.projector_type" + hasLLaVAProjectorKey = "clip.has_llava_projector" + hasMiniCPMVProjector = "clip.has_minicpmv_projector" + miniCPMVVersionKey = "clip.minicpmv_version" + hasGLMProjectorKey = "clip.has_glm_projector" + hasQwen2VLMergerKey = "clip.has_qwen2vl_merger" + hasTextEncoderKey = "clip.has_text_encoder" + hasVisionEncoderKey = "clip.has_vision_encoder" + visionImageSizeKey = "clip.vision.image_size" + visionPatchSizeKey = "clip.vision.patch_size" + visionProjectionDim = "clip.vision.projection_dim" + visionMMPatchMergeType = "clip.vision.mm_patch_merge_type" + + textEmbeddingLengthKey = "clip.text.embedding_length" + textBlockCountKey = "clip.text.block_count" + textFeedForwardLengthKey = "clip.text.feed_forward_length" + textAttentionHeadCountKey = "clip.text.attention.head_count" + textAttentionLayerNormRMSEpsilonKey = "clip.text.attention.layer_norm_epsilon" + + visionEmbeddingLengthKey = "clip.vision.embedding_length" + visionBlockCountKey = "clip.vision.block_count" + visionFeedForwardLengthKey = "clip.vision.feed_forward_length" + visionAttentionHeadCountKey = "clip.vision.attention.head_count" + visionAttentionLayerNormRMSEpsilonKey = "clip.vision.attention.layer_norm_epsilon" + ) + + ga.Type = "projector" + ga.Architecture = "clip" + + m, _ := gf.Header.MetadataKV.Index([]string{ + projectorTypeKey, + hasLLaVAProjectorKey, + hasMiniCPMVProjector, + miniCPMVVersionKey, + hasGLMProjectorKey, + hasQwen2VLMergerKey, + hasTextEncoderKey, + hasVisionEncoderKey, + visionImageSizeKey, + visionPatchSizeKey, + visionProjectionDim, + visionMMPatchMergeType, + textEmbeddingLengthKey, + textBlockCountKey, + textFeedForwardLengthKey, + textAttentionHeadCountKey, + textAttentionLayerNormRMSEpsilonKey, + visionEmbeddingLengthKey, + visionBlockCountKey, + visionFeedForwardLengthKey, + visionAttentionHeadCountKey, + visionAttentionLayerNormRMSEpsilonKey, + }) + + if v, ok := m[projectorTypeKey]; ok { + ga.ClipProjectorType = v.ValueString() + } else { + ga.ClipProjectorType = "mlp" + } + if v, ok := m[hasLLaVAProjectorKey]; ok { + ga.ClipHasLLaVAProjector = v.ValueBool() + } + if v, ok := m[hasMiniCPMVProjector]; ok { + ga.ClipHasMiniCPMVProjector = v.ValueBool() + } + if v, ok := m[miniCPMVVersionKey]; ok { + ga.ClipMiniCPMVVersion = ValueNumeric[int32](v) + } + if v, ok := m[hasGLMProjectorKey]; ok { + ga.ClipHasGLMProjector = v.ValueBool() + } + if v, ok := m[hasQwen2VLMergerKey]; ok { + ga.ClipHasQwen2VLMerger = v.ValueBool() + } + if v, ok := m[hasTextEncoderKey]; ok { + ga.ClipHasTextEncoder = v.ValueBool() + } + if v, ok := m[hasVisionEncoderKey]; ok { + ga.ClipHasVisionEncoder = v.ValueBool() + } + if v, ok := m[visionImageSizeKey]; ok { + ga.ClipVisionImageSize = ValueNumeric[uint32](v) + } + if v, ok := m[visionPatchSizeKey]; ok { + ga.ClipVisionPatchSize = ValueNumeric[uint32](v) + } + if v, ok := m[visionProjectionDim]; ok { + ga.ClipVisionProjectionDim = ValueNumeric[uint32](v) + } + ga.ClipVisionMMPatchMergeType = "flat" + if v, ok := m[visionMMPatchMergeType]; ok { + ga.ClipVisionMMPatchMergeType = v.ValueString() + } + + if v, ok := m[textEmbeddingLengthKey]; ok { + ga.EmbeddingLength = ValueNumeric[uint64](v) + } + if v, ok := m[textBlockCountKey]; ok { + ga.BlockCount = ValueNumeric[uint64](v) + } + if v, ok := m[textFeedForwardLengthKey]; ok { + if v.ValueType == GGUFMetadataValueTypeArray { + ga.FeedForwardLength = ValuesNumeric[uint64](v.ValueArray()) + } else { + vx := ValueNumeric[uint64](v) + ga.FeedForwardLength = make([]uint64, ga.BlockCount) + for i := range ga.FeedForwardLength { + ga.FeedForwardLength[i] = vx + } + } + } + if v, ok := m[textAttentionHeadCountKey]; ok { + ga.AttentionHeadCount = ValueNumeric[uint64](v) + } + if v, ok := m[textAttentionLayerNormRMSEpsilonKey]; ok { + ga.AttentionLayerNormRMSEpsilon = ValueNumeric[float32](v) + } + + if v, ok := m[visionEmbeddingLengthKey]; ok { + ga.EmbeddingLength = ValueNumeric[uint64](v) + } + if v, ok := m[visionBlockCountKey]; ok { + ga.BlockCount = ValueNumeric[uint64](v) + } + if v, ok := m[visionFeedForwardLengthKey]; ok { + if v.ValueType == GGUFMetadataValueTypeArray { + ga.FeedForwardLength = ValuesNumeric[uint64](v.ValueArray()) + } else { + vx := ValueNumeric[uint64](v) + ga.FeedForwardLength = make([]uint64, ga.BlockCount) + for i := range ga.FeedForwardLength { + ga.FeedForwardLength[i] = vx + } + } + } + if v, ok := m[visionAttentionHeadCountKey]; ok { + ga.AttentionHeadCount = ValueNumeric[uint64](v) + } + if v, ok := m[visionAttentionLayerNormRMSEpsilonKey]; ok { + ga.AttentionLayerNormRMSEpsilon = ValueNumeric[float32](v) + } + + ga.AttentionHeadCountKV = ga.AttentionHeadCount + + { + if ga.AttentionHeadCountKV > 0 { + ga.EmbeddingGQA = ga.AttentionHeadCount / ga.AttentionHeadCountKV + } + if ga.AttentionHeadCount > 0 { + ga.EmbeddingKeyGQA = uint64(ga.AttentionKeyLength) * ga.AttentionHeadCountKV + ga.EmbeddingValueGQA = uint64(ga.AttentionValueLength) * ga.AttentionHeadCountKV + } + if ga.Architecture == "mamba" { + ga.EmbeddingKeyGQA = uint64((ga.SSMConvolutionKernel - 1) * ga.SSMInnerSize) + ga.EmbeddingValueGQA = uint64(ga.SSMStateSize * ga.SSMInnerSize) + } + } + + return ga +} + +func (gf *GGUFFile) adapterArchitecture(arch string) (ga GGUFArchitecture) { + var ( + typeKey = "adapter.type" + + loraAlphaKey = "adapter.lora.alpha" + + controlVectorLayerCountKey = "adapter.control_vector.layer_count" + controlVectorLayerCountKey2 = "control_vector.layer_count" + ) + + ga.Type = "adapter" + ga.Architecture = arch + + m, _ := gf.Header.MetadataKV.Index([]string{ + typeKey, + loraAlphaKey, + controlVectorLayerCountKey, + controlVectorLayerCountKey2, + }) + + if v, ok := m[typeKey]; ok { + ga.AdapterType = v.ValueString() + } + if v, ok := m[loraAlphaKey]; ok { + ga.AdapterLoRAAlpha = ValueNumeric[float32](v) + } + if v, ok := m[controlVectorLayerCountKey]; ok { + ga.AdapterControlVectorLayerCount = ValueNumeric[uint32](v) + } else if v, ok := m[controlVectorLayerCountKey2]; ok { + ga.AdapterControlVectorLayerCount = ValueNumeric[uint32](v) + } + + return ga +} + +func (gf *GGUFFile) transformerArchitecture(arch string) (ga GGUFArchitecture) { + var ( + contextLengthKey = arch + ".context_length" + embeddingLengthKey = arch + ".embedding_length" + blockCountKey = arch + ".block_count" + feedForwardLengthKey = arch + ".feed_forward_length" + + expertFeedForwardLengthKey = arch + ".expert_feed_forward_length" + expertSharedFeedForwardLengthKey = arch + ".expert_shared_feed_forward_length" + expertCountKey = arch + ".expert_count" + expertUsedCountKey = arch + ".expert_used_count" + + attentionHeadCountKey = arch + ".attention.head_count" + attentionHeadCountKVKey = arch + ".attention.head_count_kv" + attentionMaxALiBIBiasKey = arch + ".attention.max_alibi_bias" + attentionMaxALiBIBiasKey2 = arch + ".attention.alibi_bias_max" + attentionClampKQVKey = arch + ".attention.clamp_kqv" + attentionClampKQVKey2 = arch + ".attention.clip_kqv" + attentionLayerNormEpsilonKey = arch + ".attention.layer_norm_epsilon" + attentionLayerNormRMSEpsilonKey = arch + ".attention.layer_norm_rms_epsilon" + attentionKeyLengthKey = arch + ".attention.key_length" + attentionValueLengthKey = arch + ".attention.value_length" + attentionCausalKey = arch + ".attention.causal" + + ropeDimensionCountKey = arch + ".rope.dimension_count" + ropeFrequencyBaseKey = arch + ".rope.freq_base" + ropeScaleLinearKey = arch + ".rope.scale_linear" + ropeScalingTypeKey = arch + ".rope.scaling.type" + ropeScalingFactorKey = arch + ".rope.scaling.factor" + ropeScalingOriginalContextKey = arch + ".rope.scaling.original_context_length" // uint32 maybe + ropeScalingFinetunedKey = arch + ".rope.scaling.finetuned" + + ssmConvolutionKernelKey = arch + ".ssm.conv_kernel" + ssmInnerSizeKey = arch + ".ssm.inner_size" + ssmStateSizeKey = arch + ".ssm.state_size" + ssmTimeStepRankKey = arch + ".ssm.time_step_rank" + + vocabularyLengthKey = arch + ".vocab_size" + tokenizerGGMLTokensKey = "tokenizer.ggml.tokens" + ) + + ga.Type = "model" + ga.Architecture = arch + + m, _ := gf.Header.MetadataKV.Index([]string{ + contextLengthKey, + embeddingLengthKey, + blockCountKey, + feedForwardLengthKey, + expertFeedForwardLengthKey, + expertSharedFeedForwardLengthKey, + expertCountKey, + expertUsedCountKey, + attentionHeadCountKey, + attentionHeadCountKVKey, + attentionMaxALiBIBiasKey, + attentionMaxALiBIBiasKey2, + attentionClampKQVKey, + attentionClampKQVKey2, + attentionLayerNormEpsilonKey, + attentionLayerNormRMSEpsilonKey, + attentionKeyLengthKey, + attentionValueLengthKey, + attentionCausalKey, + ropeDimensionCountKey, + ropeFrequencyBaseKey, + ropeScaleLinearKey, + ropeScalingTypeKey, + ropeScalingFactorKey, + ropeScalingOriginalContextKey, + ropeScalingFinetunedKey, + ssmConvolutionKernelKey, + ssmInnerSizeKey, + ssmStateSizeKey, + ssmTimeStepRankKey, + vocabularyLengthKey, + tokenizerGGMLTokensKey, + }) + + if v, ok := m[contextLengthKey]; ok { + ga.MaximumContextLength = ValueNumeric[uint64](v) + } + if v, ok := m[embeddingLengthKey]; ok { + ga.EmbeddingLength = ValueNumeric[uint64](v) + } + if v, ok := m[blockCountKey]; ok { + ga.BlockCount = ValueNumeric[uint64](v) + } + if v, ok := m[feedForwardLengthKey]; ok { + if v.ValueType == GGUFMetadataValueTypeArray { + ga.FeedForwardLength = ValuesNumeric[uint64](v.ValueArray()) + } else { + vx := ValueNumeric[uint64](v) + ga.FeedForwardLength = make([]uint64, ga.BlockCount) + for i := range ga.FeedForwardLength { + ga.FeedForwardLength[i] = vx + } + } + } + + if v, ok := m[expertCountKey]; ok { + ga.ExpertCount = ValueNumeric[uint32](v) + } + if v, ok := m[expertUsedCountKey]; ok { + ga.ExpertUsedCount = ValueNumeric[uint32](v) + } + if v, ok := m[expertFeedForwardLengthKey]; ok { + ga.ExpertFeedForwardLength = ValueNumeric[uint64](v) + } + if v, ok := m[expertSharedFeedForwardLengthKey]; ok { + ga.ExpertSharedFeedForwardLength = ValueNumeric[uint64](v) + } + + if v, ok := m[attentionHeadCountKey]; ok { + if v.ValueType == GGUFMetadataValueTypeArray { + ga.AttentionHeadCount = ValuesNumeric[uint64](v.ValueArray())[0] + } else { + ga.AttentionHeadCount = ValueNumeric[uint64](v) + } + } + if v, ok := m[attentionHeadCountKVKey]; ok { + if v.ValueType == GGUFMetadataValueTypeArray { + ga.AttentionHeadCountKV = ValuesNumeric[uint64](v.ValueArray())[0] + } else { + ga.AttentionHeadCountKV = ValueNumeric[uint64](v) + } + } else { + ga.AttentionHeadCountKV = ga.AttentionHeadCount + } + if v, ok := m[attentionMaxALiBIBiasKey]; ok { + ga.AttentionMaxALiBIBias = ValueNumeric[float32](v) + } else if v, ok := m[attentionMaxALiBIBiasKey2]; ok { + ga.AttentionMaxALiBIBias = ValueNumeric[float32](v) + } + if v, ok := m[attentionClampKQVKey]; ok { + ga.AttentionClampKQV = ValueNumeric[float32](v) + } else if v, ok := m[attentionClampKQVKey2]; ok { + ga.AttentionClampKQV = ValueNumeric[float32](v) + } + if v, ok := m[attentionLayerNormEpsilonKey]; ok { + ga.AttentionLayerNormEpsilon = ValueNumeric[float32](v) + } + if v, ok := m[attentionLayerNormRMSEpsilonKey]; ok { + ga.AttentionLayerNormRMSEpsilon = ValueNumeric[float32](v) + } + if v, ok := m[attentionKeyLengthKey]; ok { + ga.AttentionKeyLength = ValueNumeric[uint32](v) + } else if ga.AttentionHeadCount != 0 { + ga.AttentionKeyLength = uint32(ga.EmbeddingLength / ga.AttentionHeadCount) + } + if v, ok := m[attentionValueLengthKey]; ok { + ga.AttentionValueLength = ValueNumeric[uint32](v) + } else if ga.AttentionHeadCount != 0 { + ga.AttentionValueLength = uint32(ga.EmbeddingLength / ga.AttentionHeadCount) + } + if v, ok := m[attentionCausalKey]; ok { + ga.AttentionCausal = v.ValueBool() + } else { + ga.AttentionCausal = true + } + + if v, ok := m[ropeDimensionCountKey]; ok { + ga.RoPEDimensionCount = ValueNumeric[uint64](v) + } + if v, ok := m[ropeFrequencyBaseKey]; ok { + ga.RoPEFrequencyBase = ValueNumeric[float32](v) + } + if v, ok := m[ropeScaleLinearKey]; ok { + ga.RoPEScalingType = "linear" + ga.RoPEScalingFactor = ValueNumeric[float32](v) + } + if v, ok := m[ropeScalingTypeKey]; ok { + ga.RoPEScalingType = v.ValueString() + } + if v, ok := m[ropeScalingFactorKey]; ok { + ga.RoPEScalingFactor = ValueNumeric[float32](v) + } + if v, ok := m[ropeScalingOriginalContextKey]; ok { + ga.RoPEScalingOriginalContextLength = ValueNumeric[uint64](v) + } + if v, ok := m[ropeScalingFinetunedKey]; ok { + ga.RoPEScalingFinetuned = v.ValueBool() + } + + if v, ok := m[ssmConvolutionKernelKey]; ok { + ga.SSMConvolutionKernel = ValueNumeric[uint32](v) + } + if v, ok := m[ssmInnerSizeKey]; ok { + ga.SSMInnerSize = ValueNumeric[uint32](v) + } + if v, ok := m[ssmStateSizeKey]; ok { + ga.SSMStateSize = ValueNumeric[uint32](v) + } + if v, ok := m[ssmTimeStepRankKey]; ok { + ga.SSMTimeStepRank = ValueNumeric[uint32](v) + } + + if v, ok := m[vocabularyLengthKey]; ok { + ga.VocabularyLength = ValueNumeric[uint64](v) + } else if v, ok := m[tokenizerGGMLTokensKey]; ok { + ga.VocabularyLength = v.ValueArray().Len + } + + { + if ga.AttentionHeadCountKV > 0 { + ga.EmbeddingGQA = ga.AttentionHeadCount / ga.AttentionHeadCountKV + } + if ga.AttentionHeadCount > 0 { + ga.EmbeddingKeyGQA = uint64(ga.AttentionKeyLength) * ga.AttentionHeadCountKV + ga.EmbeddingValueGQA = uint64(ga.AttentionValueLength) * ga.AttentionHeadCountKV + } + if ga.Architecture == "mamba" { + ga.EmbeddingKeyGQA = uint64((ga.SSMConvolutionKernel - 1) * ga.SSMInnerSize) + ga.EmbeddingValueGQA = uint64(ga.SSMStateSize * ga.SSMInnerSize) + } + } + + return ga +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_estimate__llamacpp.go b/vendor/github.com/gpustack/gguf-parser-go/file_estimate__llamacpp.go new file mode 100644 index 00000000..5a49aa37 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_estimate__llamacpp.go @@ -0,0 +1,1289 @@ +package gguf_parser + +import ( + "regexp" + "slices" + "strings" + + "github.com/gpustack/gguf-parser-go/util/anyx" + "github.com/gpustack/gguf-parser-go/util/ptr" + "github.com/gpustack/gguf-parser-go/util/slicex" +) + +// Types for LLaMACpp estimation. +type ( + // LLaMACppRunEstimate represents the estimated result of loading the GGUF file in llama.cpp. + LLaMACppRunEstimate struct { + // Type describes what type this GGUF file is. + Type string `json:"type"` + // Architecture describes what architecture this GGUF file implements. + // + // All lowercase ASCII. + Architecture string `json:"architecture"` + // ClipProjectorType is the type of the projector used in the clip model. + // + // Only used when Architecture is "clip". + ClipProjectorType string `json:"clipProjectorType,omitempty"` + // AdapterType is the type of the adapter. + // + // Only used when Architecture is "adapter". + AdapterType string `json:"adapterType,omitempty"` + // FlashAttention is the flag to indicate whether enable the flash attention, + // true for enable. + FlashAttention bool `json:"flashAttention"` + // ContextSize is the size of the context. + ContextSize uint64 `json:"contextSize"` + // OffloadLayers is the number of offloaded layers. + OffloadLayers uint64 `json:"offloadLayers"` + // FullOffloaded is the flag to indicate whether the layers are fully offloaded, + // false for partial offloaded or zero offloaded. + FullOffloaded bool `json:"fullOffloaded"` + // NoMMap is the flag to indicate whether support the mmap, + // true for support. + NoMMap bool `json:"noMMap"` + // EmbeddingOnly is the flag to indicate whether the model is used for embedding only, + // true for embedding only. + EmbeddingOnly bool `json:"embeddingOnly"` + // Reranking is the flag to indicate whether the model is used for reranking, + // true for reranking. + // + // Only available when EmbeddingOnly is true. + Reranking bool `json:"reranking"` + // Distributable is the flag to indicate whether the model is distributable, + // true for distributable. + Distributable bool `json:"distributable"` + // LogicalBatchSize is the logical batch size. + LogicalBatchSize int32 `json:"logicalBatchSize"` + // PhysicalBatchSize is the physical batch size. + PhysicalBatchSize int32 `json:"physicalBatchSize"` + // Devices represents the usage for running the GGUF file, + // the first device is the CPU, and the rest are GPUs. + Devices []LLaMACppRunDeviceUsage `json:"devices"` + // Drafter is the estimated result of drafter. + Drafter *LLaMACppRunEstimate `json:"drafter,omitempty"` + // Projector is the estimated result of multimodal projector. + Projector *LLaMACppRunEstimate `json:"projector,omitempty"` + // Adapters is the estimated result of adapters. + Adapters []LLaMACppRunEstimate `json:"adapters,omitempty"` + // MaximumTokensPerSecond represents the maximum tokens per second for running the GGUF file. + MaximumTokensPerSecond *GGUFTokensPerSecondScalar `json:"maximumTokensPerSecond,omitempty"` + } + + // LLaMACppRunDeviceUsage represents the usage for running the GGUF file in llama.cpp. + LLaMACppRunDeviceUsage struct { + // HandleLayers is the number of layers that the device can handle. + HandleLayers uint64 `json:"handleLayers"` + // HandleLastLayer is the index of the last layer the device can handle. + HandleLastLayer int `json:"handleLastLayer"` + // HandleOutputLayer is the flag to indicate whether the device can handle the output layer, + // true for handle. + HandleOutputLayer bool `json:"handleOutputLayer"` + // Remote is the flag to indicate whether the device is remote, + // true for remote. + Remote bool `json:"remote"` + // Position is the relative position of the device, + // starts from 0. + // + // If Remote is true, Position is the position of the remote devices, + // Otherwise, Position is the position of the device in the local devices. + Position int `json:"position"` + // Footprint is the memory footprint for bootstrapping. + Footprint GGUFBytesScalar `json:"footprint"` + // Parameter is the running parameters that the device processes. + Parameter LLaMACppParameterUsage `json:"parameter"` + // Weight is the memory usage of weights that the device loads. + Weight LLaMACppWeightMemoryUsage `json:"weight"` + // KVCache is the memory usage of kv that the device caches. + KVCache LLaMACppKVCacheMemoryUsage `json:"kvCache"` + // Computation is the memory usage of computation that the device processes. + Computation LLaMACppComputationMemoryUsage `json:"computation"` + } + + // LLaMACppParameterUsage represents the parameter usage for running the GGUF file in llama.cpp. + LLaMACppParameterUsage struct { + // KVCache is the parameter usage for caching previous KV. + KVCache GGUFParametersScalar `json:"kvCache"` + // Input is the parameter usage for input tensors. + Input GGUFParametersScalar `json:"input"` + // Compute is the parameter usage for compute tensors. + Compute GGUFParametersScalar `json:"compute"` + // Output is the parameter usage for output tensors. + Output GGUFParametersScalar `json:"output"` + } + + // LLaMACppWeightMemoryUsage represents the memory usage of loading weights in llama.cpp. + LLaMACppWeightMemoryUsage struct { + // Input is the memory usage for loading input tensors. + Input GGUFBytesScalar `json:"input"` + // Compute is the memory usage for loading compute tensors. + Compute GGUFBytesScalar `json:"compute"` + // Output is the memory usage for loading output tensors. + Output GGUFBytesScalar `json:"output"` + } + + // LLaMACppKVCacheMemoryUsage represents the memory usage of caching previous KV in llama.cpp. + LLaMACppKVCacheMemoryUsage struct { + // Key is the memory usage for caching previous keys. + Key GGUFBytesScalar `json:"key"` + // Value is the memory usage for caching previous values. + Value GGUFBytesScalar `json:"value"` + } + + // LLaMACppComputationMemoryUsage represents the memory usage of computation in llama.cpp. + LLaMACppComputationMemoryUsage struct { + // Footprint is the memory footprint for computation. + Footprint GGUFBytesScalar `json:"footprint"` + // Input is the memory usage for input. + Input GGUFBytesScalar `json:"input"` + // Compute is the memory usage for computation. + Compute GGUFBytesScalar `json:"graph"` + // Output is the memory usage for output. + Output GGUFBytesScalar `json:"output"` + } +) + +// EstimateLLaMACppRun returns the inference estimated result of the GGUF file. +func (gf *GGUFFile) EstimateLLaMACppRun(opts ...GGUFRunEstimateOption) (e LLaMACppRunEstimate) { + // Options + var o _GGUFRunEstimateOptions + for _, opt := range opts { + opt(&o) + } + switch { + case o.TensorSplitFraction == nil: + o.TensorSplitFraction = []float64{1} + o.MainGPUIndex = 0 + case o.MainGPUIndex < 0 || o.MainGPUIndex >= len(o.TensorSplitFraction): + panic("main device index must be range of 0 to the length of tensor split fraction") + } + if len(o.DeviceMetrics) > 0 { + for i, j := 0, len(o.DeviceMetrics)-1; i < len(o.TensorSplitFraction)-j; i++ { + o.DeviceMetrics = append(o.DeviceMetrics, o.DeviceMetrics[j]) + } + o.DeviceMetrics = o.DeviceMetrics[:len(o.TensorSplitFraction)+1] + } + if o.LMCCacheKeyType == nil { + o.LMCCacheKeyType = ptr.To(GGMLTypeF16) + } + if o.LMCCacheValueType == nil { + o.LMCCacheValueType = ptr.To(GGMLTypeF16) + } + if o.LMCOffloadKVCache == nil { + o.LMCOffloadKVCache = ptr.To(true) + } + if o.LMCLogicalBatchSize == nil { + o.LMCLogicalBatchSize = ptr.To(int32(2048)) + } else { + // See https://github.com/ggerganov/llama.cpp/blob/0bf16de07b0692e7df26b9a633e232bbd66e0360/src/llama.cpp#L16519-L16525. + o.LMCLogicalBatchSize = ptr.To(max(32, *o.LMCLogicalBatchSize)) + } + if o.LMCPhysicalBatchSize == nil { + o.LMCPhysicalBatchSize = ptr.To(int32(512)) + } + if *o.LMCPhysicalBatchSize > *o.LMCLogicalBatchSize { + panic("physical batch size must be less than or equal to logical batch size") + } + if o.LMCSplitMode >= _LLAMACppSplitModeMax { + panic("split mode must be less than max") + } + + // Devices. + e.Devices = make([]LLaMACppRunDeviceUsage, len(o.TensorSplitFraction)+1) + for i := range e.Devices { + e.Devices[i].HandleLastLayer = -1 + } + for j := range e.Devices[1:] { + e.Devices[j+1].Remote = j < len(o.RPCServers) + if e.Devices[j+1].Remote { + e.Devices[j+1].Position = j + } else { + e.Devices[j+1].Position = j - len(o.RPCServers) + } + } + + // Metadata. + a := gf.Architecture() + e.Type = a.Type + e.Architecture = a.Architecture + e.ClipProjectorType = a.ClipProjectorType + e.AdapterType = a.AdapterType + + switch a.Type { + case "model": + t := gf.Tokenizer() + gf.estimateLLaMACppRunInModel(&o, &a, &t, &e) + case "projector": + // For projector model, + // see https://github.com/ggerganov/llama.cpp/blob/148ec970b62c3c5ae0a8bfdaad2fc237aaae350d/examples/llava/clip.cpp#L994-L1008. + if ptr.Deref(o.LMCOffloadLayers, a.BlockCount) != 0 { + // None model means full offload. + o.LMCOffloadLayers = ptr.To(a.BlockCount) + } else { + // None model means zero offload. + o.LMCOffloadLayers = ptr.To[uint64](0) + } + gf.estimateLLaMACppRunInProjector(&o, &a, &e) + case "adapter": + gf.estimateLLaMaCppRunInAdapter(&o, &a, &e) + } + + return e +} + +// estimateLLaMACppRunInModel estimates the inference result of the GGUF file in llama.cpp for model type, +// including the usages of footprint, weight, KV cache, and computation. +func (gf *GGUFFile) estimateLLaMACppRunInModel(o *_GGUFRunEstimateOptions, a *GGUFArchitecture, t *GGUFTokenizer, e *LLaMACppRunEstimate) { + ls := gf.Layers() + ioLs, tfLs, _ := ls.Cut([]string{ + "position_*", + "token_*", + "cls.*", + "output.*", + "output_*", + "rope_factors_*", + }) + ipLs, opLs, _ := ioLs.Cut([]string{ + "position_*", + "token_*", + }) + + if a.BlockCount == 0 { + a.BlockCount = uint64(len(tfLs)) + } + + // Full offload: nLoadLayers == 0 && isOffloadOutputLayer + // Zero offload: nOffloadLayers == 0 + // Partial offload: !Full offload && !Zero offload + var ( + nOffloadLayers uint64 + nActualOffloadLayers uint64 + nLoadLayers = a.BlockCount + idxOutputDevice int + + fullOffload, zeroOffload bool + ) + { + var isOffloadOutputLayer bool + + switch v := o.LMCOffloadLayers; { + case v == nil: + o.LMCOffloadLayers = ptr.To(a.BlockCount) + nOffloadLayers = a.BlockCount + isOffloadOutputLayer = true + case *v != 0: + nOffloadLayers = *v + if nOffloadLayers > a.BlockCount { + isOffloadOutputLayer = true + nOffloadLayers = a.BlockCount + } + } + nActualOffloadLayers = nOffloadLayers + if isOffloadOutputLayer { + nActualOffloadLayers += 1 + } + nLoadLayers -= nOffloadLayers + + fullOffload = nLoadLayers == 0 && isOffloadOutputLayer + zeroOffload = nOffloadLayers == 0 + + e.FullOffloaded = fullOffload + e.OffloadLayers = nOffloadLayers + + for i, j, offloadStart := 0, 0, len(tfLs)-int(nOffloadLayers); i < len(tfLs); i++ { + switch { + case i < int(nLoadLayers): + e.Devices[0].HandleLayers += 1 + e.Devices[0].HandleLastLayer = i + case i >= offloadStart: + x := float64(i-offloadStart) / float64(nActualOffloadLayers) + j = slicex.UpperBound(o.TensorSplitFraction, x) + e.Devices[j+1].HandleLayers += 1 + e.Devices[j+1].HandleLastLayer = i + if fullOffload && i == len(tfLs)-1 { + idxOutputDevice = j + 1 + } + } + } + + e.Devices[idxOutputDevice].HandleOutputLayer = true + } + + // Flash attention. + { + // Grok is not compatible with flash attention, + // see https://github.com/ggerganov/llama.cpp/blob/19d3c8293b1f61acbe2dab1d49a17950fd788a4a/src/llama.cpp#L9566-L9569. + if a.Architecture == "grok" { + o.FlashAttention = false + } + // Attention key length must be equal to attention value length, + // see https://github.com/ggerganov/llama.cpp/blob/19d3c8293b1f61acbe2dab1d49a17950fd788a4a/src/llama.cpp#L9571-L9574. + if a.AttentionKeyLength != a.AttentionValueLength { + o.FlashAttention = false + } + // Fallback to FP16 if the value type is quantized when disabling flash attention, + // see https://github.com/ggerganov/llama.cpp/blob/19d3c8293b1f61acbe2dab1d49a17950fd788a4a/src/llama.cpp#L9576-L9579. + if o.LMCCacheValueType.IsQuantized() && !o.FlashAttention { + o.LMCCacheValueType = ptr.To(GGMLTypeF16) + } + + e.FlashAttention = o.FlashAttention + } + + // Embedding. + if !a.AttentionCausal { + e.EmbeddingOnly = true + // Set context size/physical batch size/logical batch size to the training context size. + o.LMCContextSize = ptr.To(min(int32(a.MaximumContextLength), ptr.Deref(o.LMCContextSize, int32(a.MaximumContextLength)))) + o.LMCLogicalBatchSize = o.LMCContextSize + o.LMCPhysicalBatchSize = o.LMCLogicalBatchSize + // Reranking. + if _, found := gf.TensorInfos.Index([]string{"cls.bias", "cls.weight"}); found > 0 { + e.Reranking = true + } + } + + // Distributable, + // fix by https://github.com/ggerganov/llama.cpp/pull/11047. + e.Distributable = true + + // Batch size. + e.LogicalBatchSize = *o.LMCLogicalBatchSize + e.PhysicalBatchSize = *o.LMCPhysicalBatchSize + + // Init hyperparameters, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L6957-L7000. + var ( + nContext uint64 + nTokens uint64 + nBatch uint64 + nOutputs uint64 + nParallel uint64 + nKV uint64 + ) + { + nContext = a.MaximumContextLength + if o.LMCContextSize != nil { + nContext = uint64(*o.LMCContextSize) + } + if o.LMCInMaxContextSize { + nContext = min(nContext, a.MaximumContextLength) + } + // Padding context size, + // see https://github.com/ggerganov/llama.cpp/blob/278d0e18469aacf505be18ce790a63c7cc31be26/src/llama.cpp#L19001-L19002. + if o.FlashAttention { + nContext = GGMLPadding(nContext, 256) + } else { + nContext = GGMLPadding(nContext, 32) + } + // Correct token size, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L12221-L12224. + nTokens = min(nContext, uint64(*o.LMCPhysicalBatchSize)) + nBatch = nTokens + nOutputs = nTokens + nParallel = uint64(ptr.Deref(o.ParallelSize, 1)) + nKV = nContext + + // For mamba, + // see https://github.com/ggerganov/llama.cpp/blob/7672adeec7a79ea271058c63106c142ba84f951a/llama.cpp#L16122-L16129. + if a.Architecture == "mamba" { + nKV = nParallel + o.LMCCacheKeyType = ptr.To(GGMLTypeF32) + o.LMCCacheValueType = ptr.To(GGMLTypeF32) + } + + e.ContextSize = nContext + } + + // Footprint. + { + // Bootstrap. + e.Devices[0].Footprint = GGUFBytesScalar(5*1024*1024) /* model load */ + (gf.Size - gf.ModelSize) /* metadata */ + + // Tokens, + // https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L6380-L6384. + fp := t.TokensLength * (4 /* token type */ + 4 /* token score*/) + if t.Model == "gpt2" { + fp += t.MergesLength * (48 /* key type */ + 56 /* value type */) + } + fp += t.TokensLength * (32 /* id to token vector */ + (24 + 32) /* token to id map*/) + e.Devices[0].Footprint += GGUFBytesScalar(fp) + + // Output buffer, + // see https://github.com/ggerganov/llama.cpp/blob/7672adeec7a79ea271058c63106c142ba84f951a/llama.cpp#L11940-L12003. + ob := 4 /* float32 size */ * (a.VocabularyLength + a.EmbeddingLength) * nParallel + if fullOffload { + e.Devices[idxOutputDevice].Footprint += GGUFBytesScalar(ob) + } else { + e.Devices[0].Footprint += GGUFBytesScalar(ob) + } + } + + // Weight & Parameter. + { + // Compute. + for i, j, offloadStart := 0, 0, len(tfLs)-int(nOffloadLayers); i < len(tfLs); i++ { + idx := 0 + if i >= offloadStart { + x := float64(i-offloadStart) / float64(nActualOffloadLayers) + j = slicex.UpperBound(o.TensorSplitFraction, x) + idx = j + 1 + } + e.Devices[idx].Weight.Compute += GGUFBytesScalar(tfLs[i].Bytes()) + e.Devices[idx].Parameter.Compute += GGUFParametersScalar(tfLs[i].Elements()) + } + + // IO, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L4930-L5002. + e.Devices[0].Weight.Input = GGUFBytesScalar(ipLs.Bytes()) + e.Devices[0].Parameter.Input = GGUFParametersScalar(ipLs.Elements()) + var ( + wg GGUFBytesScalar + ps GGUFParametersScalar + ) + if _, ok := opLs.Get("output.weight"); ok { + wg = GGUFBytesScalar(opLs.Bytes()) + ps = GGUFParametersScalar(opLs.Elements()) + } else if a.AttentionCausal { + wg = GGUFBytesScalar(opLs.Bytes()) + e.Devices[0].Weight.Input /* duplicate the input layer */ + ps = GGUFParametersScalar(opLs.Elements() + ipLs.Elements()) + } + e.Devices[0].Weight.Output = wg + if fullOffload { + e.Devices[idxOutputDevice].Weight.Output = wg + e.Devices[idxOutputDevice].Parameter.Output = ps + } else { + e.Devices[0].Parameter.Output = ps + } + } + + // KV cache, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L2479-L2501. + { + kps, vps := a.EmbeddingKeyGQA*nKV, a.EmbeddingValueGQA*nKV + krs, vrs := o.LMCCacheKeyType.RowSizeOf([]uint64{kps}), o.LMCCacheValueType.RowSizeOf([]uint64{vps}) + + e.Devices[0].KVCache.Key = GGUFBytesScalar(krs * nLoadLayers) + e.Devices[0].KVCache.Value = GGUFBytesScalar(vrs * nLoadLayers) + e.Devices[0].Parameter.KVCache = GGUFParametersScalar((kps + vps) * nLoadLayers) + if !*o.LMCOffloadKVCache { + e.Devices[0].KVCache.Key += GGUFBytesScalar(krs * nOffloadLayers) + e.Devices[0].KVCache.Value += GGUFBytesScalar(vrs * nOffloadLayers) + e.Devices[0].Parameter.KVCache += GGUFParametersScalar((kps + vps) * nOffloadLayers) + } else if !zeroOffload { + for i, d := range e.Devices[1:] { + e.Devices[i+1].KVCache.Key = GGUFBytesScalar(krs * d.HandleLayers) + e.Devices[i+1].KVCache.Value = GGUFBytesScalar(vrs * d.HandleLayers) + e.Devices[i+1].Parameter.KVCache = GGUFParametersScalar((kps + vps) * d.HandleLayers) + } + } + } + + // Computation. + { + // Bootstrap, compute metadata, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L16135-L16136. + cm := GGMLTensorOverhead()*GGMLComputationGraphNodesMaximum + + GGMLComputationGraphOverhead(GGMLComputationGraphNodesMaximum, false) + e.Devices[0].Computation.Footprint = GGUFBytesScalar(cm) + + // Scheduler overhead, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L16149. + e.Devices[0].Computation.Footprint += GGUFBytesScalar(4 * 1024 * 1024) + + // GGML context, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L5015-L5036. + gc := 2 /* buffer count */ * GGMLTensorOverhead() * (uint64(len(gf.TensorInfos)) + 1 + a.BlockCount*3) + e.Devices[0].Computation.Footprint += GGUFBytesScalar(gc) + + // Tensor usage, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L16149. + // + // First, get the usage of input layer, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L2279-L2290. + var ( + inpTokens = GGMLTypeI32.RowSizeOf([]uint64{nBatch}) // I32 [n_batch] + inpEmbd = GGMLTypeF32.RowSizeOf([]uint64{a.EmbeddingLength, nBatch}) // F32 [n_embd, n_batch] + inpPos = GGMLTypeI32.RowSizeOf([]uint64{nBatch}) // I32 [n_batch] + inpOutIds = GGMLTypeI32.RowSizeOf([]uint64{nOutputs}) // I32 [n_outputs], + inpKQMask = GGMLTypeF32.RowSizeOf([]uint64{nKV, nBatch}) // F32 [n_kv, n_batch] + inpSMask = GGMLTypeF32.RowSizeOf([]uint64{1, nKV}) // F32 [1, n_kv] + inpSSeq = GGMLTypeI32.RowSizeOf([]uint64{nKV, nBatch}) // I32 [n_kv, n_batch] + ) + switch { + case a.Architecture == "mamba": + e.Devices[0].Computation.Input = GGUFBytesScalar(inpTokens + inpEmbd + inpSMask + inpSSeq + inpOutIds) + default: + e.Devices[0].Computation.Input = GGUFBytesScalar(inpTokens + inpEmbd + inpPos + inpKQMask + inpOutIds) + } + if !zeroOffload { + var v GGUFBytesScalar + switch { + case a.Architecture == "mamba": + v = GGUFBytesScalar(inpEmbd + inpSMask + inpSSeq) + default: + v = GGUFBytesScalar(inpEmbd + inpPos + inpKQMask) + } + if len(o.RPCServers) == 0 && len(o.TensorSplitFraction) > 1 { + if a.ExpertCount > 0 { + v *= 2 + } else { + v *= 4 + } + } + for i := range e.Devices[1:] { + e.Devices[i+1].Computation.Input += v + } + } + // Since the steps between transformer layers are serial, + // the allocated memory can be reused for the next layer. + // So, we only consider the usage of the largest layer, + // which is the last layer by default. + switch { + case a.Architecture == "mamba": + convInc := GGMLTypeF32.RowSizeOf([]uint64{a.EmbeddingKeyGQA, nKV}) // F32 [n_embd_key_gqa, n_kv] reshape + for _, l := range tfLs[len(tfLs)-1].Search(regexp.MustCompile(`.*\.\d+\.(attn_norm|ssm_in|ssm_conv1d)\.weight`)) { + if !strings.HasSuffix(l.Name, ".ssm_conv1d.weight") { + rs := GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[l.NDimensions-1], nTokens}) + convInc += rs + continue + } + // https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L10379. + rs := GGMLTypeF32.RowSizeOf([]uint64{uint64(a.SSMInnerSize)*nTokens + uint64(a.SSMConvolutionKernel)*uint64(a.SSMInnerSize)*nKV}) + convInc += rs + } + ssmInc := uint64(0) + for _, l := range tfLs[len(tfLs)-1].Search(regexp.MustCompile(`.*\.\d+\.ssm_(dt\.weight|a)`)) { + if !strings.HasSuffix(l.Name, ".ssm_a") { + rs := GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[l.NDimensions-1], nTokens}) + ssmInc += rs + continue + } + // https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L10413. + rs := GGMLTypeF32.RowSizeOf([]uint64{uint64(a.SSMInnerSize)*nTokens + uint64(a.SSMStateSize)*uint64(a.SSMInnerSize)*nKV}) + ssmInc += rs + } + cp := GGUFBytesScalar(convInc + ssmInc) + for i := range e.Devices[1:] { + e.Devices[i+1].Computation.Compute = cp + } + default: + loadAttnInc, offloadAttnInc := uint64(0), uint64(0) + { + rs := o.LMCCacheKeyType.RowSizeOf([]uint64{uint64(a.AttentionKeyLength), nKV, a.AttentionHeadCountKV}) + loadAttnInc = rs // k-? + rs = o.LMCCacheValueType.RowSizeOf([]uint64{uint64(a.AttentionValueLength), nKV, a.AttentionHeadCountKV}) + loadAttnInc += rs // v-? + } + if o.FlashAttention { + // https://github.com/ggerganov/llama.cpp/blob/172c8256840ffd882ab9992ecedbb587d9b21f15/llama.cpp#L7387. + offloadAttnInc = GGMLTypeF16.RowSizeOf([]uint64{nKV, nTokens}) + for _, l := range tfLs[len(tfLs)-1].Search(regexp.MustCompile(`.*\.\d+\.attn_(norm|q|qkv)\.weight`)) { + if strings.HasSuffix(l.Name, ".attn_norm.weight") { + rs := GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[l.NDimensions-1], nTokens}) + offloadAttnInc += rs + continue + } + rs := l.Bytes() + offloadAttnInc += rs + } + // https://github.com/ggerganov/llama.cpp/blob/172c8256840ffd882ab9992ecedbb587d9b21f15/llama.cpp#L6986-L6992. + rs := o.LMCCacheKeyType.RowSizeOf([]uint64{uint64(a.AttentionKeyLength), nKV, a.AttentionHeadCountKV}) + offloadAttnInc += rs + // https://github.com/ggerganov/llama.cpp/blob/172c8256840ffd882ab9992ecedbb587d9b21f15/llama.cpp#L7000-L7007. + rs = o.LMCCacheValueType.RowSizeOf([]uint64{uint64(a.AttentionValueLength), nKV, a.AttentionHeadCountKV}) + offloadAttnInc += rs + } else { + offloadAttnInc = uint64(0) + for _, l := range tfLs[len(tfLs)-1].Search(regexp.MustCompile(`.*\.\d+\.attn_(norm|q|qkv|q_b)\.weight`)) { + var rs uint64 + switch { + default: // norm. + rs = GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[l.NDimensions-1], nTokens}) + offloadAttnInc += rs + case strings.HasSuffix(l.Name, ".attn_q.weight"): + rs = GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[0], nTokens}) + offloadAttnInc += rs * 2 // Qcur. + rs = GGMLTypeF32.RowSizeOf([]uint64{nKV, nTokens, a.AttentionHeadCount}) + offloadAttnInc += rs // kq. + if !zeroOffload && !fullOffload { + offloadAttnInc += loadAttnInc + } + case strings.HasSuffix(l.Name, ".attn_qkv.weight"): + rs = GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[0], nTokens}) + offloadAttnInc += rs * 2 // Qcur. + rs = GGMLTypeF32.RowSizeOf([]uint64{nKV, nTokens, a.AttentionHeadCount}) + offloadAttnInc += rs // kq. + rs = GGMLTypeF32.RowSizeOf([]uint64{a.EmbeddingLength, a.EmbeddingLength * 3}) + offloadAttnInc += rs // wqkv. + if !zeroOffload && !fullOffload { + offloadAttnInc += loadAttnInc + } + case strings.HasSuffix(l.Name, ".attn_q_b.weight"): + rs = GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[l.NDimensions-1], nTokens}) + offloadAttnInc += rs * 2 // q-? + rs = GGMLTypeF32.RowSizeOf([]uint64{nKV, nTokens, a.AttentionHeadCount}) + offloadAttnInc += rs // kq. + } + } + } + ffnInc := uint64(0) + for _, l := range tfLs[len(tfLs)-1].Search(regexp.MustCompile(`.*\.\d+\.(attn_norm|ffn_norm|ffn_gate|ffn_up)\.weight`)) { + rs := GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[l.NDimensions-1], nTokens}) + ffnInc += rs + } + if a.ExpertCount > 0 || a.ExpertUsedCount > 0 { + rs := GGMLTypeF32.RowSizeOf([]uint64{uint64(a.ExpertCount), a.EmbeddingLength}) + ffnInc += rs // ffn_gate_input + rs = GGMLTypeF32.RowSizeOf([]uint64{uint64(a.ExpertCount), nTokens}) + ffnInc += rs // ffn_moe_logits + rs = GGMLTypeF32.RowSizeOf([]uint64{a.EmbeddingLength, uint64(a.ExpertUsedCount), nTokens}) + ffnInc += rs // ffn_moe_down + } + if !zeroOffload { + e.Devices[0].Computation.Compute = GGUFBytesScalar(loadAttnInc + ffnInc) + } else { + e.Devices[0].Computation.Compute = GGUFBytesScalar(loadAttnInc) + } + if !zeroOffload { + cp := GGUFBytesScalar(max(offloadAttnInc, ffnInc)) + for i := range e.Devices[1:] { + e.Devices[i+1].Computation.Compute = cp + } + if nLoadLayers > 1 { + for i := range e.Devices[1:] { + if e.Devices[i+1].Remote { + continue + } + e.Devices[i+1].Computation.Compute += GGUFBytesScalar(loadAttnInc) + break + } + } + } + } + // Finally, get the usage of output layer. + if a.AttentionCausal { + var outInc uint64 + if a.Architecture == "mamba" { + outInc += inpSMask + inpSSeq + } + if l, ok := opLs.Get("output.weight"); ok { + rs := GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[l.NDimensions-1], nTokens}) + outInc += rs + } else if l, ok := ipLs.Get("token_embd.weight"); ok { + rs := GGMLTypeF32.RowSizeOf([]uint64{l.Dimensions[l.NDimensions-1], nTokens}) + outInc += rs + } + e.Devices[idxOutputDevice].Computation.Output += GGUFBytesScalar(outInc) + } + } + + // Drafter. + e.Drafter = o.LMCDrafter + + // Projector. + e.Projector = o.LMCProjector + + // Adapters. + e.Adapters = o.LMCAdapters + + // Maximum tokens per second. + if ds, dmss := e.Devices, o.DeviceMetrics; len(dmss) != 0 { + ltss := make([]float64, len(dmss)) + bs := anyx.Number[float64](*o.LMCLogicalBatchSize) / float64(nBatch) + for i, dm := range dmss { + fl, upbw, dwbw := float64(max(dm.FLOPS, 1)), float64(max(dm.UpBandwidth, 1)), float64(max(dm.DownBandwidth, 1)) + cmpops := float64(ds[i].Parameter.Compute)*2 /* FMA */ *bs + float64(ds[i].Parameter.Input) + float64(ds[i].Parameter.Output) + cmps := float64(ds[i].Weight.Sum()) + cmplat := max(cmpops/fl, cmps/upbw) + kvcops := float64(ds[i].Parameter.KVCache) * 2 /* FMA */ * bs + kvcs := float64(ds[i].KVCache.Sum()) * bs + kvclat := max(kvcops/fl, kvcs/upbw) + ffs := float64(GGMLTypeF32.RowSizeOf([]uint64{a.EmbeddingLength, nBatch})) + ffslat := ffs / dwbw + lays := float64(ds[i].HandleLayers) + if ds[i].HandleOutputLayer { + lays += 1 + } + ltss[i] = (cmplat + kvclat + ffslat) * lays / float64(a.BlockCount+2) + } + lt := float64(0) + ltmax := slices.Max(ltss) + for i := range ltss { + lt += ltss[i] / ltmax * ltss[i] + } + e.MaximumTokensPerSecond = ptr.To(GGUFTokensPerSecondScalar(1 / lt)) + } +} + +func (gf *GGUFFile) estimateLLaMACppRunInProjector(o *_GGUFRunEstimateOptions, a *GGUFArchitecture, e *LLaMACppRunEstimate) { + ls := gf.Layers() + ioLs, tfLs, _ := ls.Cut([]string{ + "v.patch_embd.*", + "v.class_embd", + "v.position_embd.*", + "v.pre_ln.*", + "model.*", + "v.post_ln.*", + "mm.*", + "resampler.*", + }) + ipLs, opLs, _ := ioLs.Cut([]string{ + "v.patch_embd.*", + "v.class_embd", + "v.position_embd.*", + "v.pre_ln.*", + "model.*", + }) + + if a.BlockCount == 0 { + a.BlockCount = uint64(len(tfLs)) + } + + e.FullOffloaded = *o.LMCOffloadLayers == a.BlockCount + e.OffloadLayers = *o.LMCOffloadLayers + + // Init hyperparameters, + // see https://github.com/ggerganov/llama.cpp/blob/0827b2c1da299805288abbd556d869318f2b121e/examples/llava/clip.cpp#L599-L636. + var ( + imgHeightSize uint64 + imgWidthSize uint64 + imgPatchSize uint64 + nPatchesHeight uint64 + nPatchesWidth uint64 + nPatches uint64 + imgPatchesMaxSize uint64 + imgPatches uint64 + projectionDim uint64 // NB(thxCode): do not sure if there is the correct name. + ) + { + // See https://github.com/ggerganov/llama.cpp/blob/0827b2c1da299805288abbd556d869318f2b121e/examples/llava/llava.cpp#L397-L411, + // https://github.com/ggerganov/llama.cpp/blob/0827b2c1da299805288abbd556d869318f2b121e/examples/llava/clip.cpp#L2323-L2345, + // https://github.com/ggerganov/llama.cpp/blob/0827b2c1da299805288abbd556d869318f2b121e/examples/llava/clip.cpp#L2767-L2794. + imgHeightSize = uint64(a.ClipVisionImageSize) + imgWidthSize = imgHeightSize + imgPatchSize = uint64(a.ClipVisionPatchSize) + if a.ClipHasQwen2VLMerger { + imgHeightSize = uint64(ptr.Deref(o.LMCVisualMaxImageSize, 224)) + imgWidthSize = imgHeightSize + } + nPatchesHeight = imgHeightSize / imgPatchSize + nPatchesWidth = imgWidthSize / imgPatchSize + nPatches = nPatchesHeight * nPatchesWidth + imgPatchesMaxSize = 1 + imgPatches = nPatches + switch { + case a.ClipHasLLaVAProjector: + // LLaVA 1.6 uses up to 6 patches + if a.ClipVisionMMPatchMergeType != "flat" { + imgPatchesMaxSize = 6 + } + case a.ClipHasMiniCPMVProjector: + // MiniCPM-V uses up to 10 patches + imgPatchesMaxSize = 10 + case a.ClipProjectorType == "adapter": + // Granite vision uses up to 10 patches + base patch + imgPatchesMaxSize = 11 + } + switch a.ClipProjectorType { + case "ldp": + imgPatches /= 4 + if ti, ok := gf.TensorInfos.Get("mm.model.mb_block.1.block.2.1.bias"); ok { + projectionDim = ti.Dimensions[0] + } + case "ldpv2": + imgPatches /= 4 + if ti, ok := gf.TensorInfos.Get("mm.model.peg.0.bias"); ok { + projectionDim = ti.Dimensions[0] + } + case "mlp": + if ti, ok := gf.TensorInfos.Get("mm.2.bias"); ok { + projectionDim = ti.Dimensions[0] + } + case "mlp_norm": + if ti, ok := gf.TensorInfos.Get("mm.3.bias"); ok { + projectionDim = ti.Dimensions[0] + } + case "resampler": + if ti, ok := gf.TensorInfos.Get("resampler.query"); ok { + imgPatches = ti.Dimensions[1] + projectionDim = ti.Dimensions[0] + } + case "adapter": + if ti, ok := gf.TensorInfos.Get("adapter.linear.dense_4h_to_h.weight"); ok { + projectionDim = ti.Dimensions[1] + } + case "qwen2vl_merger": + nSizePatch := uint64(a.ClipVisionPatchSize * 2) + imgHeightPatchSize := imgHeightSize / nSizePatch + if imgHeightSize%nSizePatch > 0 { + imgHeightPatchSize++ + } + imgWidthPatchSize := imgWidthSize / nSizePatch + if imgWidthSize%nSizePatch > 0 { + imgWidthPatchSize++ + } + imgPatches = imgHeightPatchSize * imgWidthPatchSize + if ti, ok := gf.TensorInfos.Get("mm.2.bias"); ok { + projectionDim = ti.Dimensions[0] + } + case "gemma3": + if ti, ok := gf.TensorInfos.Get("mm.input_projection.weight"); ok { + imgPatches = 256 + projectionDim = ti.Dimensions[0] + } + } + } + + // Footprint. + { + // Bootstrap. + e.Devices[0].Footprint = GGUFBytesScalar(5*1024*1024) /* model load */ + (gf.Size - gf.ModelSize) /* metadata */ + + // Image Embed, + // see https://github.com/ggerganov/llama.cpp/blob/0827b2c1da299805288abbd556d869318f2b121e/examples/llava/llava.cpp#L401-L407. + e.Devices[0].Footprint += GGUFBytesScalar(imgPatchesMaxSize * imgPatches * projectionDim * 4 /* float32 size */) + } + + idx := 0 // Default to the main host's RAM. + if *o.LMCOffloadLayers != 0 { + for i := 1; i < len(e.Devices); i++ { + if !e.Devices[i].Remote { + idx = i + break + } + } + } + + // Weight & Parameter. + { + // Compute. + e.Devices[idx].HandleLayers = *o.LMCOffloadLayers + e.Devices[idx].HandleLastLayer = int(e.Devices[idx].HandleLayers - 1) + e.Devices[idx].Weight.Compute = GGUFBytesScalar(tfLs.Bytes()) + e.Devices[idx].Parameter.Compute = GGUFParametersScalar(tfLs.Elements()) + + // IO. + e.Devices[idx].Weight.Input = GGUFBytesScalar(ipLs.Bytes()) + e.Devices[idx].Parameter.Input = GGUFParametersScalar(ipLs.Elements()) + e.Devices[idx].Weight.Output = GGUFBytesScalar(opLs.Bytes()) + e.Devices[idx].Parameter.Output = GGUFParametersScalar(opLs.Elements()) + } + + // Computation. + { + // Bootstrap, compute metadata, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L16135-L16136. + cm := GGMLTensorOverhead()*GGMLComputationGraphNodesMaximum + + GGMLComputationGraphOverhead(GGMLComputationGraphNodesMaximum, false) + e.Devices[0].Computation.Footprint = GGUFBytesScalar(cm) + + // Scheduler overhead, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L16149. + e.Devices[0].Computation.Footprint += GGUFBytesScalar(4 * 1024 * 1024) + + // GGML context, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L5015-L5036. + gc := 2 /* buffer count */ * GGMLTensorOverhead() * (uint64(len(gf.TensorInfos)) + 1 + a.BlockCount*3) + e.Devices[0].Computation.Footprint += GGUFBytesScalar(gc) + + // Tensor usage. + var ( + hasClassEmbd bool + nPositions uint64 + nPositionIDs uint64 + nBatch uint64 + nEmbd uint64 + nHead uint64 + ) + { + _, hasClassEmbd = ipLs.Get("v.class_embd") + nPositions = nPatches + if hasClassEmbd { + nPositions += 1 + } + nPositionIDs = nPositions + if a.ClipHasQwen2VLMerger { + nPositionIDs *= 4 + } + nBatch = 1 + nEmbd = a.EmbeddingLength + nHead = a.AttentionHeadCount + } + // First, get the usage of input layer. + var ( + inpRaw = GGMLTypeF32.RowSizeOf([]uint64{imgWidthSize, imgHeightSize, 3, nBatch}) // F32 [img_width, img_height, 3, n_batch] + inpRawCnt = GGMLTypeF32.RowSizeOf([]uint64{nPatches, nEmbd, nBatch}) // I32 [n_patches, n_embd, n_batch] + inpEmbd = GGMLTypeF32.RowSizeOf([]uint64{nEmbd, nPositions, nBatch}) // F32 [n_embd, n_positions, n_batch] + inpPosEmbd = GGMLTypeF32.RowSizeOf([]uint64{projectionDim, nPatchesHeight * nPatchesWidth, nBatch}) // F32 [mmproj, pos_h * pos_w, n_batch] + inpPos = GGMLTypeI32.RowSizeOf([]uint64{nPositionIDs}) // I32 [n_positions] + inpPatches = GGMLTypeI32.RowSizeOf([]uint64{nPatches}) // I32 [n_patches] + ) + { + e.Devices[idx].Computation.Input = GGUFBytesScalar(inpRaw + inpRawCnt + inpPos + inpPatches) + if a.ClipHasMiniCPMVProjector { + e.Devices[idx].Computation.Input += GGUFBytesScalar(inpPosEmbd) + } + if hasClassEmbd { + e.Devices[idx].Computation.Input += GGUFBytesScalar(inpEmbd) + } + } + // Since the steps between transformer layers are serial, + // the allocated memory can be reused for the next layer. + // So, we only consider the usage of a certain layer. + { + compNorm := GGMLTypeF32.RowSizeOf([]uint64{nEmbd, nPositions}) * 2 + compVcur := GGMLTypeF32.RowSizeOf([]uint64{nEmbd, nPositions}) + compKcur := GGMLTypeF32.RowSizeOf([]uint64{nEmbd, nPositions}) + compKQcur := GGMLTypeF32.RowSizeOf([]uint64{nPositions, nPositions, nHead}) + e.Devices[idx].Computation.Compute = GGUFBytesScalar(compNorm + compVcur + compKcur + compKQcur) + } + } +} + +func (gf *GGUFFile) estimateLLaMaCppRunInAdapter(o *_GGUFRunEstimateOptions, a *GGUFArchitecture, e *LLaMACppRunEstimate) { + ls := gf.Layers() + ioLs, tfLs, _ := ls.Cut([]string{ + "position_*", + "token_*", + "cls.*", + "output.*", + "output_*", + }) + ipLs, opLs, _ := ioLs.Cut([]string{ + "position_*", + "token_*", + }) + + if a.BlockCount == 0 { + a.BlockCount = uint64(len(tfLs)) + } + + // Full offload: nLoadLayers == 0 && isOffloadOutputLayer + // Zero offload: nOffloadLayers == 0 + // Partial offload: !Full offload && !Zero offload + var ( + nOffloadLayers uint64 + nActualOffloadLayers uint64 + nLoadLayers = a.BlockCount + idxOutputDevice int + + fullOffload bool + ) + { + var isOffloadOutputLayer bool + + switch v := o.LMCOffloadLayers; { + case v == nil: + o.LMCOffloadLayers = ptr.To(a.BlockCount) + nOffloadLayers = a.BlockCount + isOffloadOutputLayer = true + case *v != 0: + nOffloadLayers = *v + if nOffloadLayers > a.BlockCount { + isOffloadOutputLayer = true + nOffloadLayers = a.BlockCount + } + } + nActualOffloadLayers = nOffloadLayers + if isOffloadOutputLayer { + nActualOffloadLayers += 1 + } + nLoadLayers -= nOffloadLayers + + fullOffload = nLoadLayers == 0 && isOffloadOutputLayer + + e.FullOffloaded = fullOffload + e.OffloadLayers = nOffloadLayers + + for i, j, offloadStart := 0, 0, len(tfLs)-int(nOffloadLayers); i < len(tfLs); i++ { + switch { + case i < int(nLoadLayers): + e.Devices[0].HandleLayers += 1 + e.Devices[0].HandleLastLayer = i + case i >= offloadStart: + x := float64(i-offloadStart) / float64(nActualOffloadLayers) + j = slicex.UpperBound(o.TensorSplitFraction, x) + e.Devices[j+1].HandleLayers += 1 + e.Devices[j+1].HandleLastLayer = i + if fullOffload && i == len(tfLs)-1 { + idxOutputDevice = j + 1 + } + } + } + + e.Devices[idxOutputDevice].HandleOutputLayer = true + } + + // Distributable. + e.Distributable = false + + // Footprint. + { + // Bootstrap. + e.Devices[0].Footprint = GGUFBytesScalar(5*1024*1024) /* model load */ + (gf.Size - gf.ModelSize) /* metadata */ + } + + // Weight & Parameter. + { + // Compute. + for i, j, offloadStart := 0, 0, len(tfLs)-int(nOffloadLayers); i < len(tfLs); i++ { + idx := 0 + if i >= offloadStart { + x := float64(i-offloadStart) / float64(nActualOffloadLayers) + j = slicex.UpperBound(o.TensorSplitFraction, x) + idx = j + 1 + } + e.Devices[idx].Weight.Compute += GGUFBytesScalar(tfLs[i].Bytes()) + e.Devices[idx].Parameter.Compute += GGUFParametersScalar(tfLs[i].Elements()) + } + + // IO, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L4930-L5002. + e.Devices[0].Weight.Input = GGUFBytesScalar(ipLs.Bytes()) + e.Devices[0].Parameter.Input = GGUFParametersScalar(ipLs.Elements()) + var ( + wg GGUFBytesScalar + ps GGUFParametersScalar + ) + if _, ok := opLs.Get("output.weight"); ok { + wg = GGUFBytesScalar(opLs.Bytes()) + ps = GGUFParametersScalar(opLs.Elements()) + } else if a.AttentionCausal { + wg = GGUFBytesScalar(opLs.Bytes()) + e.Devices[0].Weight.Input /* duplicate the input layer */ + ps = GGUFParametersScalar(opLs.Elements() + ipLs.Elements()) + } + e.Devices[0].Weight.Output = wg + if fullOffload { + e.Devices[idxOutputDevice].Weight.Output = wg + e.Devices[idxOutputDevice].Parameter.Output = ps + } else { + e.Devices[0].Parameter.Output = ps + } + } +} + +// Types for LLaMACpp estimated summary. +type ( + // LLaMACppRunEstimateSummary represents the summary of the usage for loading the GGUF file in llama.cpp. + LLaMACppRunEstimateSummary struct { + /* Basic */ + + // Items + Items []LLaMACppRunEstimateSummaryItem `json:"items"` + + /* Appendix */ + + // Type describes what type this GGUF file is. + Type string `json:"type"` + // Architecture describes what architecture this GGUF file implements. + // + // All lowercase ASCII. + Architecture string `json:"architecture"` + // ClipProjectorType is the type of the projector used in the clip model. + // + // Only used when Architecture is "clip". + ClipProjectorType string `json:"clipProjectorType,omitempty"` + // AdapterType is the type of the adapter. + // + // Only used when Architecture is "adapter". + AdapterType string `json:"adapterType,omitempty"` + // ContextSize is the size of the context. + ContextSize uint64 `json:"contextSize"` + // FlashAttention is the flag to indicate whether enable the flash attention, + // true for enable. + FlashAttention bool `json:"flashAttention"` + // NoMMap is the flag to indicate whether the file must be loaded without mmap, + // true for total loaded. + NoMMap bool `json:"noMMap"` + // EmbeddingOnly is the flag to indicate whether the model is used for embedding only, + // true for embedding only. + EmbeddingOnly bool `json:"embeddingOnly"` + // Reranking is the flag to indicate whether the model is used for reranking, + // true for reranking. + // + // Only available when EmbeddingOnly is true. + Reranking bool `json:"reranking"` + // Distributable is the flag to indicate whether the model is distributable, + // true for distributable. + Distributable bool `json:"distributable"` + // LogicalBatchSize is the logical batch size. + LogicalBatchSize int32 `json:"logicalBatchSize"` + // PhysicalBatchSize is the physical batch size. + PhysicalBatchSize int32 `json:"physicalBatchSize"` + } + + // LLaMACppRunEstimateSummaryItem represents one summary item for loading the GGUF file in llama.cpp. + LLaMACppRunEstimateSummaryItem struct { + // OffloadLayers is the number of offloaded layers. + OffloadLayers uint64 `json:"offloadLayers"` + // FullOffloaded is the flag to indicate whether the layers are fully offloaded, + // false for partial offloaded or zero offloaded. + FullOffloaded bool `json:"fullOffloaded"` + // MaximumTokensPerSecond is the maximum tokens per second for running the GGUF file. + MaximumTokensPerSecond *GGUFTokensPerSecondScalar `json:"maximumTokensPerSecond,omitempty"` + // RAM is the memory usage for loading the GGUF file in RAM. + RAM LLaMACppRunEstimateMemory `json:"ram"` + // VRAMs is the memory usage for loading the GGUF file in VRAM per device. + VRAMs []LLaMACppRunEstimateMemory `json:"vrams"` + } + + // LLaMACppRunEstimateMemory represents the memory usage for loading the GGUF file in llama.cpp. + LLaMACppRunEstimateMemory struct { + // HandleLayers is the number of layers that the device can handle. + HandleLayers uint64 `json:"handleLayers"` + // HandleLastLayer is the index of the last layer the device can handle. + HandleLastLayer int `json:"handleLastLayer"` + // HandleOutputLayer is the flag to indicate whether the device can handle the output layer, + // true for handle. + HandleOutputLayer bool `json:"handleOutputLayer"` + // Remote is the flag to indicate whether the device is remote, + // true for remote. + Remote bool `json:"remote"` + // Position is the relative position of the device, + // starts from 0. + // + // If Remote is true, Position is the position of the remote devices, + // Otherwise, Position is the position of the device in the local devices. + Position int `json:"position"` + // UMA represents the usage of Unified Memory Architecture. + UMA GGUFBytesScalar `json:"uma"` + // NonUMA represents the usage of Non-Unified Memory Architecture. + NonUMA GGUFBytesScalar `json:"nonuma"` + } +) + +// SummarizeItem returns the corresponding LLaMACppRunEstimateSummaryItem with the given options. +func (e LLaMACppRunEstimate) SummarizeItem(mmap bool, nonUMARamFootprint, nonUMAVramFootprint uint64) (emi LLaMACppRunEstimateSummaryItem) { + emi.OffloadLayers, emi.FullOffloaded = e.OffloadLayers, e.FullOffloaded + if emi.FullOffloaded { + emi.OffloadLayers++ // The output layer is offloaded. + } + emi.MaximumTokensPerSecond = e.MaximumTokensPerSecond + + // RAM. + { + fp := e.Devices[0].Footprint + wg := e.Devices[0].Weight.Sum() + kv := e.Devices[0].KVCache.Sum() + cp := e.Devices[0].Computation.Sum() + + emi.RAM.HandleLayers = e.Devices[0].HandleLayers + emi.RAM.HandleLastLayer = e.Devices[0].HandleLastLayer + emi.RAM.HandleOutputLayer = e.Devices[0].HandleOutputLayer + + // UMA. + emi.RAM.UMA = fp + wg + kv + cp + if !e.NoMMap && (mmap || e.FullOffloaded) { + emi.RAM.UMA -= wg + if !mmap { + emi.RAM.UMA += e.Devices[0].Weight.Output + } + } + + // NonUMA. + emi.RAM.NonUMA = GGUFBytesScalar(nonUMARamFootprint) + emi.RAM.UMA + } + + // VRAMs. + emi.VRAMs = make([]LLaMACppRunEstimateMemory, len(e.Devices)-1) + { + for i, d := range e.Devices[1:] { + fp := d.Footprint + wg := d.Weight.Sum() + kv := d.KVCache.Sum() + cp := d.Computation.Sum() + + emi.VRAMs[i].HandleLayers = d.HandleLayers + emi.VRAMs[i].HandleLastLayer = d.HandleLastLayer + emi.VRAMs[i].HandleOutputLayer = d.HandleOutputLayer + emi.VRAMs[i].Remote = d.Remote + emi.VRAMs[i].Position = d.Position + + // UMA. + emi.VRAMs[i].UMA = fp + wg + kv + /* cp */ 0 + if !e.NoMMap && mmap { + emi.VRAMs[i].UMA -= wg + if d.Remote || d.Position > 0 && d.HandleLastLayer >= 0 || e.Type == "projector" { + emi.VRAMs[i].UMA += wg + } + } + + // NonUMA. + emi.VRAMs[i].NonUMA = GGUFBytesScalar(nonUMAVramFootprint) + fp + wg + kv + cp + if !d.Remote && d.Position > 0 && d.HandleLastLayer < 0 { + emi.VRAMs[i].NonUMA -= wg + cp + } + } + } + + // Add drafter's usage. + if e.Drafter != nil { + demi := e.Drafter.SummarizeItem(mmap, 0, 0) + emi.RAM.UMA += demi.RAM.UMA + emi.RAM.NonUMA += demi.RAM.NonUMA + for i, v := range demi.VRAMs { + emi.VRAMs[i].UMA += v.UMA + emi.VRAMs[i].NonUMA += v.NonUMA + } + } + + // Add projector's usage. + if e.Projector != nil { + pemi := e.Projector.SummarizeItem(mmap, 0, 0) + emi.RAM.UMA += pemi.RAM.UMA + emi.RAM.NonUMA += pemi.RAM.NonUMA + for i, v := range pemi.VRAMs { + emi.VRAMs[i].UMA += v.UMA + emi.VRAMs[i].NonUMA += v.NonUMA + } + } + + // Add adapters' usage. + for i := range e.Adapters { + aemi := e.Adapters[i].SummarizeItem(false, 0, 0) + emi.RAM.UMA += aemi.RAM.UMA + emi.RAM.NonUMA += aemi.RAM.NonUMA + for j, v := range aemi.VRAMs { + emi.VRAMs[j].UMA += v.UMA + emi.VRAMs[j].NonUMA += v.NonUMA + } + } + + return emi +} + +// Summarize returns the corresponding LLaMACppRunEstimateSummary with the given options. +func (e LLaMACppRunEstimate) Summarize(mmap bool, nonUMARamFootprint, nonUMAVramFootprint uint64) (es LLaMACppRunEstimateSummary) { + // Items. + es.Items = []LLaMACppRunEstimateSummaryItem{ + e.SummarizeItem(mmap, nonUMARamFootprint, nonUMAVramFootprint), + } + + // Just copy from the original estimate. + es.Type = e.Type + es.Architecture = e.Architecture + es.ClipProjectorType = e.ClipProjectorType + es.AdapterType = e.AdapterType + es.ContextSize = e.ContextSize + es.FlashAttention = e.FlashAttention + es.NoMMap = e.NoMMap + es.EmbeddingOnly = e.EmbeddingOnly + es.Reranking = e.Reranking + es.LogicalBatchSize = e.LogicalBatchSize + es.PhysicalBatchSize = e.PhysicalBatchSize + es.Distributable = e.Distributable + + return es +} + +func (u LLaMACppWeightMemoryUsage) Sum() GGUFBytesScalar { + return u.Input + u.Compute + u.Output +} + +func (u LLaMACppKVCacheMemoryUsage) Sum() GGUFBytesScalar { + return u.Key + u.Value +} + +func (u LLaMACppComputationMemoryUsage) Sum() GGUFBytesScalar { + return u.Footprint + u.Input + max(u.Compute, u.Output) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_estimate__stablediffusioncpp.go b/vendor/github.com/gpustack/gguf-parser-go/file_estimate__stablediffusioncpp.go new file mode 100644 index 00000000..dc1dd0f2 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_estimate__stablediffusioncpp.go @@ -0,0 +1,581 @@ +package gguf_parser + +import ( + "math" + "strings" + + "golang.org/x/exp/maps" + + "github.com/gpustack/gguf-parser-go/util/ptr" + "github.com/gpustack/gguf-parser-go/util/stringx" +) + +// Types for StableDiffusionCpp estimation. +type ( + // StableDiffusionCppRunEstimate represents the estimated result of loading the GGUF file in stable-diffusion.cpp. + StableDiffusionCppRunEstimate struct { + // Type describes what type this GGUF file is. + Type string `json:"type"` + // Architecture describes what architecture this GGUF file implements. + // + // All lowercase ASCII. + Architecture string `json:"architecture"` + // FlashAttention is the flag to indicate whether enable the flash attention, + // true for enable. + FlashAttention bool `json:"flashAttention"` + // FullOffloaded is the flag to indicate whether the layers are fully offloaded, + // false for partial offloaded or zero offloaded. + FullOffloaded bool `json:"fullOffloaded"` + // NoMMap is the flag to indicate whether support the mmap, + // true for support. + NoMMap bool `json:"noMMap"` + // ImageOnly is the flag to indicate whether the model is used for generating image, + // true for generating image only. + ImageOnly bool `json:"imageOnly"` + // Distributable is the flag to indicate whether the model is distributable, + // true for distributable. + Distributable bool `json:"distributable"` + // Devices represents the usage for running the GGUF file, + // the first device is the CPU, and the rest are GPUs. + Devices []StableDiffusionCppRunDeviceUsage `json:"devices"` + // Autoencoder is the estimated result of the autoencoder. + Autoencoder *StableDiffusionCppRunEstimate `json:"autoencoder,omitempty"` + // Conditioners is the estimated result of the conditioners. + Conditioners []StableDiffusionCppRunEstimate `json:"conditioners,omitempty"` + // Upscaler is the estimated result of the upscaler. + Upscaler *StableDiffusionCppRunEstimate `json:"upscaler,omitempty"` + // ControlNet is the estimated result of the control net. + ControlNet *StableDiffusionCppRunEstimate `json:"controlNet,omitempty"` + } + + // StableDiffusionCppRunDeviceUsage represents the usage for running the GGUF file in llama.cpp. + StableDiffusionCppRunDeviceUsage struct { + // Remote is the flag to indicate whether the device is remote, + // true for remote. + Remote bool `json:"remote"` + // Position is the relative position of the device, + // starts from 0. + // + // If Remote is true, Position is the position of the remote devices, + // Otherwise, Position is the position of the device in the local devices. + Position int `json:"position"` + // Footprint is the memory footprint for bootstrapping. + Footprint GGUFBytesScalar `json:"footprint"` + // Parameter is the running parameters that the device processes. + Parameter GGUFParametersScalar `json:"parameter"` + // Weight is the memory usage of weights that the device loads. + Weight GGUFBytesScalar `json:"weight"` + // Computation is the memory usage of computation that the device processes. + Computation GGUFBytesScalar `json:"computation"` + } +) + +func (gf *GGUFFile) EstimateStableDiffusionCppRun(opts ...GGUFRunEstimateOption) (e StableDiffusionCppRunEstimate) { + // Options + var o _GGUFRunEstimateOptions + for _, opt := range opts { + opt(&o) + } + switch { + case o.TensorSplitFraction == nil: + o.TensorSplitFraction = []float64{1} + o.MainGPUIndex = 0 + case o.MainGPUIndex < 0 || o.MainGPUIndex >= len(o.TensorSplitFraction): + panic("main device index must be range of 0 to the length of tensor split fraction") + } + if len(o.DeviceMetrics) > 0 { + for i, j := 0, len(o.DeviceMetrics)-1; i < len(o.TensorSplitFraction)-j; i++ { + o.DeviceMetrics = append(o.DeviceMetrics, o.DeviceMetrics[j]) + } + o.DeviceMetrics = o.DeviceMetrics[:len(o.TensorSplitFraction)+1] + } + if o.SDCOffloadLayers == nil { + o.SDCOffloadLayers = ptr.To[uint64](math.MaxUint64) + } + if o.SDCBatchCount == nil { + o.SDCBatchCount = ptr.To[int32](1) + } + if o.SDCHeight == nil { + o.SDCHeight = ptr.To[uint32](1024) + } + if o.SDCWidth == nil { + o.SDCWidth = ptr.To[uint32](1024) + } + if o.SDCOffloadConditioner == nil { + o.SDCOffloadConditioner = ptr.To(true) + } + if o.SDCOffloadAutoencoder == nil { + o.SDCOffloadAutoencoder = ptr.To(true) + } + if o.SDCAutoencoderTiling == nil { + o.SDCAutoencoderTiling = ptr.To(false) + } + if o.SDCFreeComputeMemoryImmediately == nil { + o.SDCFreeComputeMemoryImmediately = ptr.To(false) + } + + // Devices. + initDevices := func(e *StableDiffusionCppRunEstimate) { + for j := range e.Devices[1:] { + e.Devices[j+1].Remote = j < len(o.RPCServers) + if e.Devices[j+1].Remote { + e.Devices[j+1].Position = j + } else { + e.Devices[j+1].Position = j - len(o.RPCServers) + } + } + } + e.Devices = make([]StableDiffusionCppRunDeviceUsage, len(o.TensorSplitFraction)+1) + initDevices(&e) + + // Metadata. + a := gf.Architecture() + e.Type = a.Type + e.Architecture = normalizeArchitecture(a.DiffusionArchitecture) + + // Flash attention. + if o.FlashAttention && !strings.HasPrefix(a.DiffusionArchitecture, "Stable Diffusion 3") { + // NB(thxCode): Stable Diffusion 3 doesn't support flash attention yet, + // see https://github.com/leejet/stable-diffusion.cpp/pull/386. + e.FlashAttention = true + } + + // Distributable. + e.Distributable = true + + // Offload. + e.FullOffloaded = *o.SDCOffloadLayers > 0 + + // NoMMap. + e.NoMMap = true // TODO: Implement this. + + // ImageOnly. + e.ImageOnly = true // TODO: Implement this. + + // Autoencoder. + if a.DiffusionAutoencoder != nil { + ae := &StableDiffusionCppRunEstimate{ + Type: "model", + Architecture: e.Architecture + "_vae", + FlashAttention: e.FlashAttention, + Distributable: e.Distributable, + FullOffloaded: e.FullOffloaded && *o.SDCOffloadAutoencoder, + NoMMap: e.NoMMap, + Devices: make([]StableDiffusionCppRunDeviceUsage, len(e.Devices)), + } + initDevices(ae) + e.Autoencoder = ae + } + + // Conditioners. + if len(a.DiffusionConditioners) != 0 { + e.Conditioners = make([]StableDiffusionCppRunEstimate, 0, len(a.DiffusionConditioners)) + for i := range a.DiffusionConditioners { + cd := StableDiffusionCppRunEstimate{ + Type: "model", + Architecture: normalizeArchitecture(a.DiffusionConditioners[i].Architecture), + FlashAttention: e.FlashAttention, + Distributable: e.Distributable, + FullOffloaded: e.FullOffloaded && *o.SDCOffloadConditioner, + NoMMap: e.NoMMap, + Devices: make([]StableDiffusionCppRunDeviceUsage, len(e.Devices)), + } + initDevices(&cd) + e.Conditioners = append(e.Conditioners, cd) + } + } + + // Footprint + { + // Bootstrap. + e.Devices[0].Footprint = GGUFBytesScalar(10*1024*1024) /* model load */ + (gf.Size - gf.ModelSize) /* metadata */ + } + + var cdLs, aeLs, dmLs GGUFLayerTensorInfos + { + ls := gf.Layers() + cdLs, aeLs, _ = ls.Cut([]string{ + "cond_stage_model.*", + }) + aeLs, dmLs, _ = aeLs.Cut([]string{ + "first_stage_model.*", + }) + } + + var cdDevIdx, aeDevIdx, dmDevIdx int + { + if *o.SDCOffloadConditioner && *o.SDCOffloadLayers > 0 { + cdDevIdx = 1 + } + if *o.SDCOffloadAutoencoder && *o.SDCOffloadLayers > 0 { + aeDevIdx = 1 + if len(e.Devices) > 3 { + aeDevIdx = 2 + } + } + if *o.SDCOffloadLayers > 0 { + dmDevIdx = 1 + switch { + case len(e.Devices) > 3: + dmDevIdx = 3 + case len(e.Devices) > 2: + dmDevIdx = 2 + } + } + } + + // Weight & Parameter. + { + // Conditioners. + for i := range cdLs { + e.Conditioners[i].Devices[cdDevIdx].Weight = GGUFBytesScalar(cdLs[i].Bytes()) + e.Conditioners[i].Devices[cdDevIdx].Parameter = GGUFParametersScalar(cdLs[i].Elements()) + } + + // Autoencoder. + if aeLs != nil { + e.Autoencoder.Devices[aeDevIdx].Weight = GGUFBytesScalar(aeLs.Bytes()) + e.Autoencoder.Devices[aeDevIdx].Parameter = GGUFParametersScalar(aeLs.Elements()) + } + + // Model. + e.Devices[dmDevIdx].Weight = GGUFBytesScalar(dmLs.Bytes()) + e.Devices[dmDevIdx].Parameter = GGUFParametersScalar(dmLs.Elements()) + } + + // Computation. + { + // Bootstrap, compute metadata, + // see https://github.com/ggerganov/llama.cpp/blob/d6ef0e77dd25f54fb5856af47e3926cf6f36c281/llama.cpp#L16135-L16136. + cm := GGMLTensorOverhead()*GGMLComputationGraphNodesMaximum + + GGMLComputationGraphOverhead(GGMLComputationGraphNodesMaximum, false) + e.Devices[0].Computation = GGUFBytesScalar(cm) + + // Work context, + // see https://github.com/leejet/stable-diffusion.cpp/blob/4570715727f35e5a07a76796d823824c8f42206c/stable-diffusion.cpp#L1467-L1481, + // https://github.com/leejet/stable-diffusion.cpp/blob/4570715727f35e5a07a76796d823824c8f42206c/stable-diffusion.cpp#L1572-L1586, + // https://github.com/leejet/stable-diffusion.cpp/blob/4570715727f35e5a07a76796d823824c8f42206c/stable-diffusion.cpp#L1675-L1679. + // + { + zChannels := uint64(4) + if a.DiffusionTransformer { + zChannels = 16 + } + // See https://github.com/thxCode/stable-diffusion.cpp/blob/1ae97f8a8ca3615bdaf9c1fd32c13562e2471833/stable-diffusion.cpp#L2682-L2691. + usage := uint64(128 * 1024 * 1024) /* 128MiB, LLaMA Box */ + usage += uint64(*o.SDCWidth) * uint64(*o.SDCHeight) * 3 /* output channels */ * 4 /* sizeof(float) */ * zChannels + e.Devices[0].Computation += GGUFBytesScalar(usage * uint64(ptr.Deref(o.ParallelSize, 1)) /* max batch */) + } + + // Encode usage, + // see https://github.com/leejet/stable-diffusion.cpp/blob/4570715727f35e5a07a76796d823824c8f42206c/conditioner.hpp#L388-L391, + // https://github.com/leejet/stable-diffusion.cpp/blob/4570715727f35e5a07a76796d823824c8f42206c/conditioner.hpp#L758-L766, + // https://github.com/leejet/stable-diffusion.cpp/blob/4570715727f35e5a07a76796d823824c8f42206c/conditioner.hpp#L1083-L1085. + { + var tes [][]uint64 + switch { + case strings.HasPrefix(a.DiffusionArchitecture, "FLUX"): // FLUX.1 + tes = [][]uint64{ + {768, 77}, + {4096, 256}, + } + case strings.HasPrefix(a.DiffusionArchitecture, "Stable Diffusion 3"): // SD 3.x + tes = [][]uint64{ + {768, 77}, + {1280, 77}, + {4096, 77}, + } + case strings.HasPrefix(a.DiffusionArchitecture, "Stable Diffusion XL"): // SD XL/XL Refiner + if strings.HasSuffix(a.DiffusionArchitecture, "Refiner") { + tes = [][]uint64{ + {1280, 77}, + } + } else { + tes = [][]uint64{ + {768, 77}, + {1280, 77}, + } + } + default: // SD 1.x/2.x + tes = [][]uint64{ + {768, 77}, + } + } + for i := range cdLs { + usage := GGMLTypeF32.RowSizeOf(tes[i]) * 2 /* include conditioner */ + e.Conditioners[i].Devices[cdDevIdx].Computation += GGUFBytesScalar(usage) + } + + // TODO VAE Encode + } + + // Diffusing usage. + if !*o.SDCFreeComputeMemoryImmediately { + var usage uint64 + switch { + case strings.HasPrefix(a.DiffusionArchitecture, "FLUX"): // FLUX.1 + usage = GuessFLUXDiffusionModelMemoryUsage(*o.SDCWidth, *o.SDCHeight, e.FlashAttention) + case strings.HasPrefix(a.DiffusionArchitecture, "Stable Diffusion 3"): // SD 3.x + const ( + sd3MediumKey = "model.diffusion_model.joint_blocks.23.x_block.attn.proj.weight" // SD 3 Medium + sd35MediumKey = "model.diffusion_model.joint_blocks.23.x_block.attn.ln_k.weight" // SD 3.5 Medium + sd35LargeKey = "model.diffusion_model.joint_blocks.37.x_block.attn.ln_k.weight" // SD 3.5 Large + ) + m, _ := dmLs.Index([]string{sd3MediumKey, sd35MediumKey, sd35LargeKey}) + switch { + case m[sd35LargeKey].Name != "": + usage = GuessSD35LargeDiffusionModelMemoryUsage(*o.SDCWidth, *o.SDCHeight, e.FlashAttention) + case m[sd35MediumKey].Name != "": + usage = GuessSD35MediumDiffusionModelMemoryUsage(*o.SDCWidth, *o.SDCHeight, e.FlashAttention) + default: + usage = GuessSD3MediumDiffusionModelMemoryUsage(*o.SDCWidth, *o.SDCHeight, e.FlashAttention) + } + case strings.HasPrefix(a.DiffusionArchitecture, "Stable Diffusion XL"): // SD XL/XL Refiner + const ( + sdXlKey = "model.diffusion_model.output_blocks.5.1.transformer_blocks.1.attn1.to_v.weight" // SD XL + sdXlRefinerKey = "model.diffusion_model.output_blocks.8.1.transformer_blocks.1.attn1.to_v.weight" // SD XL Refiner + ) + m, _ := dmLs.Index([]string{sdXlKey, sdXlRefinerKey}) + if m[sdXlRefinerKey].Name != "" { + usage = GuessSDXLRefinerDiffusionModelMemoryUsage(*o.SDCWidth, *o.SDCHeight, e.FlashAttention) + } else { + usage = GuessSDXLDiffusionModelMemoryUsage(*o.SDCWidth, *o.SDCHeight, e.FlashAttention) + } + case strings.HasPrefix(a.DiffusionArchitecture, "Stable Diffusion 2"): // SD 2.x + usage = GuessSD2DiffusionModelMemoryUsage(*o.SDCWidth, *o.SDCHeight, e.FlashAttention) + default: // SD 1.x + usage = GuessSD1DiffusionModelMemoryUsage(*o.SDCWidth, *o.SDCHeight, e.FlashAttention) + } + e.Devices[dmDevIdx].Computation += GGUFBytesScalar(usage) + } + + // Decode usage. + if aeLs != nil && !*o.SDCFreeComputeMemoryImmediately { + // Bootstrap. + e.Autoencoder.Devices[aeDevIdx].Footprint += GGUFBytesScalar(100 * 1024 * 1024) /*100 MiB.*/ + + var convDim uint64 + { + m, _ := aeLs.Index([]string{ + "first_stage_model.decoder.conv_in.weight", + "decoder.conv_in.weight", + }) + tis := maps.Values(m) + if len(tis) != 0 && tis[0].NDimensions > 3 { + convDim = max(tis[0].Dimensions[0], tis[0].Dimensions[3]) + } + } + + var usage uint64 + if !*o.SDCAutoencoderTiling { + usage = uint64(*o.SDCWidth) * uint64(*o.SDCHeight) * (3 /* output channels */ *4 /* sizeof(float) */ + 1) * convDim + } else { + usage = 512 * 512 * (3 /* output channels */ *4 /* sizeof(float) */ + 1) * convDim + } + e.Autoencoder.Devices[aeDevIdx].Computation += GGUFBytesScalar(usage) + } + } + + return e +} + +// Types for StableDiffusionCpp estimated summary. +type ( + // StableDiffusionCppRunEstimateSummary represents the estimated summary of loading the GGUF file in stable-diffusion.cpp. + StableDiffusionCppRunEstimateSummary struct { + /* Basic */ + + // Items + Items []StableDiffusionCppRunEstimateSummaryItem `json:"items"` + + /* Appendix */ + + // Type describes what type this GGUF file is. + Type string `json:"type"` + // Architecture describes what architecture this GGUF file implements. + // + // All lowercase ASCII. + Architecture string `json:"architecture"` + // FlashAttention is the flag to indicate whether enable the flash attention, + // true for enable. + FlashAttention bool `json:"flashAttention"` + // NoMMap is the flag to indicate whether the file must be loaded without mmap, + // true for total loaded. + NoMMap bool `json:"noMMap"` + // ImageOnly is the flag to indicate whether the model is used for generating image, + // true for embedding only. + ImageOnly bool `json:"imageOnly"` + // Distributable is the flag to indicate whether the model is distributable, + // true for distributable. + Distributable bool `json:"distributable"` + } + + // StableDiffusionCppRunEstimateSummaryItem represents the estimated summary item of loading the GGUF file in stable-diffusion.cpp. + StableDiffusionCppRunEstimateSummaryItem struct { + // FullOffloaded is the flag to indicate whether the layers are fully offloaded, + // false for partial offloaded or zero offloaded. + FullOffloaded bool `json:"fullOffloaded"` + // RAM is the memory usage for loading the GGUF file in RAM. + RAM StableDiffusionCppRunEstimateMemory `json:"ram"` + // VRAMs is the memory usage for loading the GGUF file in VRAM per device. + VRAMs []StableDiffusionCppRunEstimateMemory `json:"vrams"` + } + + // StableDiffusionCppRunEstimateMemory represents the memory usage for loading the GGUF file in llama.cpp. + StableDiffusionCppRunEstimateMemory struct { + // Remote is the flag to indicate whether the device is remote, + // true for remote. + Remote bool `json:"remote"` + // Position is the relative position of the device, + // starts from 0. + // + // If Remote is true, Position is the position of the remote devices, + // Otherwise, Position is the position of the device in the local devices. + Position int `json:"position"` + // UMA represents the usage of Unified Memory Architecture. + UMA GGUFBytesScalar `json:"uma"` + // NonUMA represents the usage of Non-Unified Memory Architecture. + NonUMA GGUFBytesScalar `json:"nonuma"` + } +) + +// SummarizeItem returns the corresponding LLaMACppRunEstimateSummaryItem with the given options. +func (e StableDiffusionCppRunEstimate) SummarizeItem( + mmap bool, + nonUMARamFootprint, nonUMAVramFootprint uint64, +) (emi StableDiffusionCppRunEstimateSummaryItem) { + emi.FullOffloaded = e.FullOffloaded + + // RAM. + { + fp := e.Devices[0].Footprint + wg := e.Devices[0].Weight + cp := e.Devices[0].Computation + + // UMA. + emi.RAM.UMA = fp + wg + cp + + // NonUMA. + emi.RAM.NonUMA = GGUFBytesScalar(nonUMARamFootprint) + emi.RAM.UMA + } + + // VRAMs. + emi.VRAMs = make([]StableDiffusionCppRunEstimateMemory, len(e.Devices)-1) + { + for i, d := range e.Devices[1:] { + fp := d.Footprint + wg := d.Weight + cp := d.Computation + + emi.VRAMs[i].Remote = d.Remote + emi.VRAMs[i].Position = d.Position + + // UMA. + emi.VRAMs[i].UMA = fp + wg + /* cp */ 0 + if d.Remote { + emi.VRAMs[i].UMA += cp + } + + // NonUMA. + emi.VRAMs[i].NonUMA = GGUFBytesScalar(nonUMAVramFootprint) + fp + wg + cp + } + } + + // Add antoencoder's usage. + if e.Autoencoder != nil { + aemi := e.Autoencoder.SummarizeItem(mmap, 0, 0) + emi.RAM.UMA += aemi.RAM.UMA + emi.RAM.NonUMA += aemi.RAM.NonUMA + for i, v := range aemi.VRAMs { + emi.VRAMs[i].UMA += v.UMA + emi.VRAMs[i].NonUMA += v.NonUMA + } + } + + // Add conditioners' usage. + for i := range e.Conditioners { + cemi := e.Conditioners[i].SummarizeItem(mmap, 0, 0) + emi.RAM.UMA += cemi.RAM.UMA + emi.RAM.NonUMA += cemi.RAM.NonUMA + for i, v := range cemi.VRAMs { + emi.VRAMs[i].UMA += v.UMA + emi.VRAMs[i].NonUMA += v.NonUMA + } + } + + // Add upscaler's usage. + if e.Upscaler != nil { + uemi := e.Upscaler.SummarizeItem(mmap, 0, 0) + emi.RAM.UMA += uemi.RAM.UMA + emi.RAM.NonUMA += uemi.RAM.NonUMA + // NB(thxCode): all VRAMs should offload to the first device at present. + var vramUMA, vramNonUMA GGUFBytesScalar + for _, v := range uemi.VRAMs { + vramUMA += v.UMA + vramNonUMA += v.NonUMA + } + if e.Upscaler.FullOffloaded { + emi.VRAMs[0].UMA += vramUMA + emi.VRAMs[0].NonUMA += vramNonUMA + } else { + emi.RAM.UMA += vramUMA + emi.RAM.NonUMA += vramNonUMA + } + } + + // Add control net's usage. + if e.ControlNet != nil { + cnemi := e.ControlNet.SummarizeItem(mmap, 0, 0) + emi.RAM.UMA += cnemi.RAM.UMA + emi.RAM.NonUMA += cnemi.RAM.NonUMA + // NB(thxCode): all VRAMs should offload to the first device at present. + var vramUMA, vramNonUMA GGUFBytesScalar + for _, v := range cnemi.VRAMs { + vramUMA += v.UMA + vramNonUMA += v.NonUMA + } + if e.ControlNet.FullOffloaded { + emi.VRAMs[0].UMA += vramUMA + emi.VRAMs[0].NonUMA += vramNonUMA + } else { + emi.RAM.UMA += vramUMA + emi.RAM.NonUMA += vramNonUMA + } + } + + return emi +} + +// Summarize returns the corresponding StableDiffusionCppRunEstimate with the given options. +func (e StableDiffusionCppRunEstimate) Summarize( + mmap bool, + nonUMARamFootprint, nonUMAVramFootprint uint64, +) (es StableDiffusionCppRunEstimateSummary) { + // Items. + es.Items = []StableDiffusionCppRunEstimateSummaryItem{ + e.SummarizeItem(mmap, nonUMARamFootprint, nonUMAVramFootprint), + } + + // Just copy from the original estimate. + es.Type = e.Type + es.Architecture = e.Architecture + es.FlashAttention = e.FlashAttention + es.NoMMap = e.NoMMap + es.ImageOnly = e.ImageOnly + es.Distributable = e.Distributable + + return es +} + +func normalizeArchitecture(arch string) string { + return stringx.ReplaceAllFunc(arch, func(r rune) rune { + switch r { + case ' ', '.', '-', '/', ':': + return '_' // Replace with underscore. + } + if r >= 'A' && r <= 'Z' { + r += 'a' - 'A' // Lowercase. + } + return r + }) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_estimate_option.go b/vendor/github.com/gpustack/gguf-parser-go/file_estimate_option.go new file mode 100644 index 00000000..9dee4bf0 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_estimate_option.go @@ -0,0 +1,361 @@ +package gguf_parser + +import ( + "slices" + + "github.com/gpustack/gguf-parser-go/util/ptr" +) + +type ( + _GGUFRunEstimateOptions struct { + // Common + ParallelSize *int32 + FlashAttention bool + MainGPUIndex int + RPCServers []string + TensorSplitFraction []float64 + DeviceMetrics []GGUFRunDeviceMetric + + // LLaMACpp (LMC) specific + LMCContextSize *int32 + LMCInMaxContextSize bool + LMCLogicalBatchSize *int32 + LMCPhysicalBatchSize *int32 + LMCVisualMaxImageSize *uint32 + LMCCacheKeyType *GGMLType + LMCCacheValueType *GGMLType + LMCOffloadKVCache *bool + LMCOffloadLayers *uint64 + LMCSplitMode LLaMACppSplitMode + LMCProjector *LLaMACppRunEstimate + LMCDrafter *LLaMACppRunEstimate + LMCAdapters []LLaMACppRunEstimate + + // StableDiffusionCpp (SDC) specific + SDCOffloadLayers *uint64 + SDCBatchCount *int32 + SDCHeight *uint32 + SDCWidth *uint32 + SDCOffloadConditioner *bool + SDCOffloadAutoencoder *bool + SDCAutoencoderTiling *bool + SDCFreeComputeMemoryImmediately *bool + SDCUpscaler *StableDiffusionCppRunEstimate + SDCControlNet *StableDiffusionCppRunEstimate + } + + // GGUFRunDeviceMetric holds the device metric for the estimate. + // + // When the device represents a CPU, + // FLOPS refers to the floating-point operations per second of that CPU, + // while UpBandwidth indicates the bandwidth of the RAM (since SRAM is typically small and cannot hold all weights, + // the RAM here refers to the bandwidth of DRAM, + // unless the device's SRAM can accommodate the corresponding model weights). + // + // When the device represents a GPU, + // FLOPS refers to the floating-point operations per second of that GPU, + // while UpBandwidth indicates the bandwidth of the VRAM. + // + // When the device represents a specific node, + // FLOPS depends on whether a CPU or GPU is being used, + // while UpBandwidth refers to the network bandwidth between nodes. + GGUFRunDeviceMetric struct { + // FLOPS is the floating-point operations per second of the device. + FLOPS FLOPSScalar + // UpBandwidth is the bandwidth of the device to transmit data to calculate, + // unit is Bps (bytes per second). + UpBandwidth BytesPerSecondScalar + // DownBandwidth is the bandwidth of the device to transmit calculated result to next layer, + // unit is Bps (bytes per second). + DownBandwidth BytesPerSecondScalar + } + + // GGUFRunEstimateOption is the options for the estimate. + GGUFRunEstimateOption func(*_GGUFRunEstimateOptions) +) + +// WithParallelSize sets the (decoding sequences) parallel size for the estimate. +func WithParallelSize(size int32) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if size <= 0 { + return + } + o.ParallelSize = &size + } +} + +// WithFlashAttention sets the flash attention flag. +func WithFlashAttention() GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.FlashAttention = true + } +} + +// WithMainGPUIndex sets the main device for the estimate. +// +// When split mode is LLaMACppSplitModeNone, the main device is the only device. +// When split mode is LLaMACppSplitModeRow, the main device handles the intermediate results and KV. +// +// WithMainGPUIndex needs to combine with WithTensorSplitFraction. +func WithMainGPUIndex(di int) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.MainGPUIndex = di + } +} + +// WithRPCServers sets the RPC servers for the estimate. +func WithRPCServers(srvs []string) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if len(srvs) == 0 { + return + } + o.RPCServers = srvs + } +} + +// WithTensorSplitFraction sets the tensor split cumulative fractions for the estimate. +// +// WithTensorSplitFraction accepts a variadic number of fractions, +// all fraction values must be in the range of [0, 1], +// and the last fraction must be 1. +// +// For example, WithTensorSplitFraction(0.2, 0.4, 0.6, 0.8, 1) will split the tensor into five parts with 20% each. +func WithTensorSplitFraction(fractions []float64) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if len(fractions) == 0 { + return + } + for _, f := range fractions { + if f < 0 || f > 1 { + return + } + } + if fractions[len(fractions)-1] != 1 { + return + } + o.TensorSplitFraction = fractions + } +} + +// WithDeviceMetrics sets the device metrics for the estimate. +func WithDeviceMetrics(metrics []GGUFRunDeviceMetric) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if len(metrics) == 0 { + return + } + o.DeviceMetrics = metrics + } +} + +// WithLLaMACppContextSize sets the context size for the estimate. +func WithLLaMACppContextSize(size int32) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if size <= 0 { + return + } + o.LMCContextSize = &size + } +} + +// WithinLLaMACppMaxContextSize limits the context size to the maximum, +// if the context size is over the maximum. +func WithinLLaMACppMaxContextSize() GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.LMCInMaxContextSize = true + } +} + +// WithLLaMACppLogicalBatchSize sets the logical batch size for the estimate. +func WithLLaMACppLogicalBatchSize(size int32) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if size <= 0 { + return + } + o.LMCLogicalBatchSize = &size + } +} + +// WithLLaMACppPhysicalBatchSize sets the physical batch size for the estimate. +func WithLLaMACppPhysicalBatchSize(size int32) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if size <= 0 { + return + } + o.LMCPhysicalBatchSize = &size + } +} + +// _GGUFEstimateCacheTypeAllowList is the allow list of cache key and value types. +var _GGUFEstimateCacheTypeAllowList = []GGMLType{ + GGMLTypeF32, + GGMLTypeF16, + GGMLTypeBF16, + GGMLTypeQ8_0, + GGMLTypeQ4_0, GGMLTypeQ4_1, + GGMLTypeIQ4_NL, + GGMLTypeQ5_0, GGMLTypeQ5_1, +} + +// WithLLaMACppCacheKeyType sets the cache key type for the estimate. +func WithLLaMACppCacheKeyType(t GGMLType) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if slices.Contains(_GGUFEstimateCacheTypeAllowList, t) { + o.LMCCacheKeyType = &t + } + } +} + +// WithLLaMACppCacheValueType sets the cache value type for the estimate. +func WithLLaMACppCacheValueType(t GGMLType) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if slices.Contains(_GGUFEstimateCacheTypeAllowList, t) { + o.LMCCacheValueType = &t + } + } +} + +// WithoutLLaMACppOffloadKVCache disables offloading the KV cache. +func WithoutLLaMACppOffloadKVCache() GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.LMCOffloadKVCache = ptr.To(false) + } +} + +// WithLLaMACppOffloadLayers sets the number of layers to offload. +func WithLLaMACppOffloadLayers(layers uint64) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.LMCOffloadLayers = &layers + } +} + +// LLaMACppSplitMode is the split mode for LLaMACpp. +type LLaMACppSplitMode uint + +const ( + LLaMACppSplitModeLayer LLaMACppSplitMode = iota + LLaMACppSplitModeRow + LLaMACppSplitModeNone + _LLAMACppSplitModeMax +) + +// WithLLaMACppSplitMode sets the split mode for the estimate. +func WithLLaMACppSplitMode(mode LLaMACppSplitMode) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if mode < _LLAMACppSplitModeMax { + o.LMCSplitMode = mode + } + } +} + +// WithLLaMACppVisualMaxImageSize sets the visual maximum image size input for the estimate. +func WithLLaMACppVisualMaxImageSize(size uint32) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if size == 0 { + return + } + o.LMCVisualMaxImageSize = &size + } +} + +// WithLLaMACppDrafter sets the drafter estimate usage. +func WithLLaMACppDrafter(dft *LLaMACppRunEstimate) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.LMCDrafter = dft + } +} + +// WithLLaMACppProjector sets the multimodal projector estimate usage. +func WithLLaMACppProjector(prj *LLaMACppRunEstimate) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.LMCProjector = prj + } +} + +// WithLLaMACppAdapters sets the adapters estimate usage. +func WithLLaMACppAdapters(adp []LLaMACppRunEstimate) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if len(adp) == 0 { + return + } + o.LMCAdapters = adp + } +} + +// WithStableDiffusionCppOffloadLayers sets the number of layers to offload. +func WithStableDiffusionCppOffloadLayers(layers uint64) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.SDCOffloadLayers = &layers + } +} + +// WithStableDiffusionCppBatchCount sets the batch count for the estimate. +func WithStableDiffusionCppBatchCount(count int32) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if count == 0 { + return + } + o.SDCBatchCount = ptr.To(count) + } +} + +// WithStableDiffusionCppHeight sets the image height for the estimate. +func WithStableDiffusionCppHeight(height uint32) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if height == 0 { + return + } + o.SDCHeight = ptr.To(height) + } +} + +// WithStableDiffusionCppWidth sets the image width for the estimate. +func WithStableDiffusionCppWidth(width uint32) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + if width == 0 { + return + } + o.SDCWidth = ptr.To(width) + } +} + +// WithoutStableDiffusionCppOffloadConditioner disables offloading the conditioner(text encoder). +func WithoutStableDiffusionCppOffloadConditioner() GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.SDCOffloadConditioner = ptr.To(false) + } +} + +// WithoutStableDiffusionCppOffloadAutoencoder disables offloading the autoencoder. +func WithoutStableDiffusionCppOffloadAutoencoder() GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.SDCOffloadAutoencoder = ptr.To(false) + } +} + +// WithStableDiffusionCppAutoencoderTiling enables tiling for the autoencoder. +func WithStableDiffusionCppAutoencoderTiling() GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.SDCAutoencoderTiling = ptr.To(true) + } +} + +// WithStableDiffusionCppFreeComputeMemoryImmediately enables freeing compute memory immediately. +func WithStableDiffusionCppFreeComputeMemoryImmediately() GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.SDCFreeComputeMemoryImmediately = ptr.To(true) + } +} + +// WithStableDiffusionCppUpscaler sets the upscaler estimate usage. +func WithStableDiffusionCppUpscaler(ups *StableDiffusionCppRunEstimate) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.SDCUpscaler = ups + } +} + +// WithStableDiffusionCppControlNet sets the control net estimate usage. +func WithStableDiffusionCppControlNet(cn *StableDiffusionCppRunEstimate) GGUFRunEstimateOption { + return func(o *_GGUFRunEstimateOptions) { + o.SDCControlNet = cn + } +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_from_distro.go b/vendor/github.com/gpustack/gguf-parser-go/file_from_distro.go new file mode 100644 index 00000000..217b1855 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_from_distro.go @@ -0,0 +1,106 @@ +package gguf_parser + +import ( + "context" + "errors" + "fmt" + "net/http" + "path/filepath" + "time" + + "github.com/gpustack/gguf-parser-go/util/httpx" +) + +var ( + ErrOllamaInvalidModel = errors.New("ollama invalid model") + ErrOllamaBaseLayerNotFound = errors.New("ollama base layer not found") +) + +// ParseGGUFFileFromOllama parses a GGUF file from Ollama model's base layer, +// and returns a GGUFFile, or an error if any. +func ParseGGUFFileFromOllama(ctx context.Context, model string, opts ...GGUFReadOption) (*GGUFFile, error) { + return ParseGGUFFileFromOllamaModel(ctx, ParseOllamaModel(model), opts...) +} + +// ParseGGUFFileFromOllamaModel is similar to ParseGGUFFileFromOllama, +// but inputs an OllamaModel instead of a string. +// +// The given OllamaModel will be completed(fetching MediaType, Config and Layers) after calling this function. +func ParseGGUFFileFromOllamaModel(ctx context.Context, model *OllamaModel, opts ...GGUFReadOption) (gf *GGUFFile, err error) { + if model == nil { + return nil, ErrOllamaInvalidModel + } + + opts = append(opts[:len(opts):len(opts)], SkipRangeDownloadDetection()) + + var o _GGUFReadOptions + for _, opt := range opts { + opt(&o) + } + + // Cache. + { + if o.CachePath != "" { + o.CachePath = filepath.Join(o.CachePath, "distro", "ollama") + } + c := GGUFFileCache(o.CachePath) + + // Get from cache. + if gf, err = c.Get(model.String(), o.CacheExpiration); err == nil { + return gf, nil + } + + // Put to cache. + defer func() { + if err == nil { + _ = c.Put(model.String(), gf) + } + }() + } + + var cli *http.Client + cli = httpx.Client( + httpx.ClientOptions(). + WithUserAgent(OllamaUserAgent()). + If(o.Debug, func(x *httpx.ClientOption) *httpx.ClientOption { + return x.WithDebug() + }). + WithTimeout(0). + WithRetryBackoff(1*time.Second, 5*time.Second, 10). + WithRetryIf(func(resp *http.Response, err error) bool { + return httpx.DefaultRetry(resp, err) || OllamaRegistryAuthorizeRetry(resp, cli) + }). + WithTransport( + httpx.TransportOptions(). + WithoutKeepalive(). + TimeoutForDial(10*time.Second). + TimeoutForTLSHandshake(5*time.Second). + If(o.SkipProxy, func(x *httpx.TransportOption) *httpx.TransportOption { + return x.WithoutProxy() + }). + If(o.ProxyURL != nil, func(x *httpx.TransportOption) *httpx.TransportOption { + return x.WithProxy(http.ProxyURL(o.ProxyURL)) + }). + If(o.SkipTLSVerification, func(x *httpx.TransportOption) *httpx.TransportOption { + return x.WithoutInsecureVerify() + }). + If(o.SkipDNSCache, func(x *httpx.TransportOption) *httpx.TransportOption { + return x.WithoutDNSCache() + }))) + + var ml OllamaModelLayer + { + err := model.Complete(ctx, cli) + if err != nil { + return nil, fmt.Errorf("complete ollama model: %w", err) + } + + var ok bool + ml, ok = model.GetLayer("application/vnd.ollama.image.model") + if !ok { + return nil, ErrOllamaBaseLayerNotFound + } + } + + return parseGGUFFileFromRemote(ctx, cli, ml.BlobURL().String(), o) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_from_remote.go b/vendor/github.com/gpustack/gguf-parser-go/file_from_remote.go new file mode 100644 index 00000000..245daaca --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_from_remote.go @@ -0,0 +1,153 @@ +package gguf_parser + +import ( + "context" + "fmt" + "io" + "net/http" + "path/filepath" + "strings" + "time" + + "github.com/gpustack/gguf-parser-go/util/httpx" + "github.com/gpustack/gguf-parser-go/util/osx" +) + +// ParseGGUFFileFromHuggingFace parses a GGUF file from Hugging Face(https://huggingface.co/), +// and returns a GGUFFile, or an error if any. +func ParseGGUFFileFromHuggingFace(ctx context.Context, repo, file string, opts ...GGUFReadOption) (*GGUFFile, error) { + ep := osx.Getenv("HF_ENDPOINT", "https://huggingface.co") + return ParseGGUFFileRemote(ctx, fmt.Sprintf("%s/%s/resolve/main/%s", ep, repo, file), opts...) +} + +// ParseGGUFFileFromModelScope parses a GGUF file from Model Scope(https://modelscope.cn/), +// and returns a GGUFFile, or an error if any. +func ParseGGUFFileFromModelScope(ctx context.Context, repo, file string, opts ...GGUFReadOption) (*GGUFFile, error) { + ep := osx.Getenv("MS_ENDPOINT", "https://modelscope.cn") + opts = append(opts[:len(opts):len(opts)], SkipRangeDownloadDetection()) + return ParseGGUFFileRemote(ctx, fmt.Sprintf("%s/models/%s/resolve/master/%s", ep, repo, file), opts...) +} + +// ParseGGUFFileRemote parses a GGUF file from a remote BlobURL, +// and returns a GGUFFile, or an error if any. +func ParseGGUFFileRemote(ctx context.Context, url string, opts ...GGUFReadOption) (gf *GGUFFile, err error) { + var o _GGUFReadOptions + for _, opt := range opts { + opt(&o) + } + + // Cache. + { + if o.CachePath != "" { + o.CachePath = filepath.Join(o.CachePath, "remote") + if o.SkipLargeMetadata { + o.CachePath = filepath.Join(o.CachePath, "brief") + } + } + c := GGUFFileCache(o.CachePath) + + // Get from cache. + if gf, err = c.Get(url, o.CacheExpiration); err == nil { + return gf, nil + } + + // Put to cache. + defer func() { + if err == nil { + _ = c.Put(url, gf) + } + }() + } + + cli := httpx.Client( + httpx.ClientOptions(). + WithUserAgent("gguf-parser-go"). + If(o.Debug, + func(x *httpx.ClientOption) *httpx.ClientOption { + return x.WithDebug() + }, + ). + If(o.BearerAuthToken != "", + func(x *httpx.ClientOption) *httpx.ClientOption { + return x.WithBearerAuth(o.BearerAuthToken) + }, + ). + WithTimeout(0). + WithTransport( + httpx.TransportOptions(). + WithoutKeepalive(). + TimeoutForDial(5*time.Second). + TimeoutForTLSHandshake(5*time.Second). + TimeoutForResponseHeader(5*time.Second). + If(o.SkipProxy, + func(x *httpx.TransportOption) *httpx.TransportOption { + return x.WithoutProxy() + }, + ). + If(o.ProxyURL != nil, + func(x *httpx.TransportOption) *httpx.TransportOption { + return x.WithProxy(http.ProxyURL(o.ProxyURL)) + }, + ). + If(o.SkipTLSVerification || !strings.HasPrefix(url, "https://"), + func(x *httpx.TransportOption) *httpx.TransportOption { + return x.WithoutInsecureVerify() + }, + ). + If(o.SkipDNSCache, + func(x *httpx.TransportOption) *httpx.TransportOption { + return x.WithoutDNSCache() + }, + ), + ), + ) + + return parseGGUFFileFromRemote(ctx, cli, url, o) +} + +func parseGGUFFileFromRemote(ctx context.Context, cli *http.Client, url string, o _GGUFReadOptions) (*GGUFFile, error) { + var urls []string + { + rs := CompleteShardGGUFFilename(url) + if rs != nil { + urls = rs + } else { + urls = []string{url} + } + } + + fs := make([]_GGUFFileReadSeeker, 0, len(urls)) + defer func() { + for i := range fs { + osx.Close(fs[i]) + } + }() + + for i := range urls { + req, err := httpx.NewGetRequestWithContext(ctx, urls[i]) + if err != nil { + return nil, fmt.Errorf("new request: %w", err) + } + + sf, err := httpx.OpenSeekerFile(cli, req, + httpx.SeekerFileOptions(). + WithBufferSize(o.BufferSize). + If(o.SkipRangeDownloadDetection, + func(x *httpx.SeekerFileOption) *httpx.SeekerFileOption { + return x.WithoutRangeDownloadDetect() + }, + ), + ) + if err != nil { + return nil, fmt.Errorf("open http file: %w", err) + } + + fs = append(fs, _GGUFFileReadSeeker{ + Closer: sf, + ReadSeeker: io.NewSectionReader(sf, 0, sf.Len()), + Size: sf.Len(), + }) + } + + return parseGGUFFile(fs, o) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_metadata.go b/vendor/github.com/gpustack/gguf-parser-go/file_metadata.go new file mode 100644 index 00000000..13e39e05 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_metadata.go @@ -0,0 +1,412 @@ +package gguf_parser + +import ( + "regexp" + "sort" + "strings" + + "golang.org/x/exp/maps" +) + +// GGUFMetadata represents the model metadata of a GGUF file. +type GGUFMetadata struct { + /* Basic */ + + // Type describes what type this GGUF file is, + // default is "model". + Type string `json:"type"` + // Architecture describes what architecture this GGUF file implements. + // + // All lowercase ASCII. + Architecture string `json:"architecture"` + // QuantizationVersion describes the version of the quantization format. + // + // Not required if the model is not quantized (i.e. no tensors are quantized). + // If any tensors are quantized, this must be present. + // This is separate to the quantization scheme of the tensors itself, + // the quantization version may change without changing the scheme's name, + // e.g. the quantization scheme is Q5_K, and the QuantizationVersion is 4. + QuantizationVersion uint32 `json:"quantizationVersion,omitempty"` + // Alignment describes the alignment of the GGUF file. + // + // This can vary to allow for different alignment schemes, but it must be a multiple of 8. + // Some writers may not write the alignment. + // + // Default is 32. + Alignment uint32 `json:"alignment"` + // Name to the model. + // + // This should be a human-readable name that can be used to identify the GGUF file. + // It should be unique within the community that the model is defined in. + Name string `json:"name,omitempty"` + // Author to the model. + Author string `json:"author,omitempty"` + // URL to the model's homepage. + // + // This can be a GitHub repo, a paper, etc. + URL string `json:"url,omitempty"` + // Description to the model. + Description string `json:"description,omitempty"` + // License to the model. + // + // This is expressed as a SPDX license expression, e.g. "MIT OR Apache-2.0". + License string `json:"license,omitempty"` + // FileType describes the type of the majority of the tensors in the GGUF file. + FileType GGUFFileType `json:"fileType"` + + /* Appendix */ + + // LittleEndian is true if the GGUF file is little-endian, + // and false for big-endian. + LittleEndian bool `json:"littleEndian"` + // FileSize is the size of the GGUF file in bytes. + FileSize GGUFBytesScalar `json:"fileSize"` + // Size is the model size. + Size GGUFBytesScalar `json:"size"` + // Parameters is the parameters of the GGUF file. + Parameters GGUFParametersScalar `json:"parameters"` + // BitsPerWeight is the bits per weight of the GGUF file. + BitsPerWeight GGUFBitsPerWeightScalar `json:"bitsPerWeight"` +} + +// GGUFFileType is a type of GGUF file, +// see https://github.com/ggerganov/llama.cpp/blob/278d0e18469aacf505be18ce790a63c7cc31be26/ggml/include/ggml.h#L404-L433. +type GGUFFileType uint32 + +// GGUFFileType constants. +// +// GGUFFileTypeMostlyQ4_2, GGUFFileTypeMostlyQ4_3 are deprecated. +// +// GGUFFileTypeMostlyQ4_1_F16 is a special case where the majority of the tensors are Q4_1, +// but 'token_embd.weight' and 'output.weight' tensors are F16. +const ( + GGUFFileTypeAllF32 GGUFFileType = iota // F32 + GGUFFileTypeMostlyF16 // F16 + GGUFFileTypeMostlyQ4_0 // Q4_0 + GGUFFileTypeMostlyQ4_1 // Q4_1 + GGUFFileTypeMostlyQ4_1_F16 // Q4_1_F16 + GGUFFileTypeMostlyQ4_2 // Q4_2 + GGUFFileTypeMostlyQ4_3 // Q4_3 + GGUFFileTypeMostlyQ8_0 // Q8_0 + GGUFFileTypeMostlyQ5_0 // Q5_0 + GGUFFileTypeMostlyQ5_1 // Q5_1 + GGUFFileTypeMostlyQ2_K // Q2_K + GGUFFileTypeMostlyQ3_K // Q3_K/Q3_K_S + GGUFFileTypeMostlyQ4_K // Q4_K/Q3_K_M + GGUFFileTypeMostlyQ5_K // Q5_K/Q3_K_L + GGUFFileTypeMostlyQ6_K // Q6_K/Q4_K_S + GGUFFileTypeMostlyIQ2_XXS // IQ2_XXS/Q4_K_M + GGUFFileTypeMostlyIQ2_XS // IQ2_XS/Q5_K_S + GGUFFileTypeMostlyIQ3_XXS // IQ3_XXS/Q5_K_M + GGUFFileTypeMostlyIQ1_S // IQ1_S/Q6_K + GGUFFileTypeMostlyIQ4_NL // IQ4_NL + GGUFFileTypeMostlyIQ3_S // IQ3_S + GGUFFileTypeMostlyIQ2_S // IQ2_S + GGUFFileTypeMostlyIQ4_XS // IQ4_XS + GGUFFileTypeMostlyIQ1_M // IQ1_M + GGUFFileTypeMostlyBF16 // BF16 + GGUFFileTypeMostlyQ4_0_4_4 // Q4_0_4x4 + GGUFFileTypeMostlyQ4_0_4_8 // Q4_0_4x8 + GGUFFileTypeMostlyQ4_0_8_8 // Q4_0_8x8 + GGUFFileTypeMostlyTQ1_0 // TQ1_0 + GGUFFileTypeMostlyTQ2_0 // TQ2_0 + GGUFFileTypeMostlyIQ4_NL_4_4 // IQ4_NL_4x4 + GGUFFileTypeMostlyIQ4_NL_4_8 // IQ4_NL_4x8 + GGUFFileTypeMostlyIQ4_NL_8_8 // IQ4_NL_8x8 + _GGUFFileTypeCount // Unknown +) + +// Metadata returns the metadata of the GGUF file. +func (gf *GGUFFile) Metadata() (gm GGUFMetadata) { + const ( + typeKey = "general.type" + architectureKey = "general.architecture" + quantizationKey = "general.quantization_version" + alignmentKey = "general.alignment" + nameKey = "general.name" + authorKey = "general.author" + urlKey = "general.url" + descriptionKey = "general.description" + licenseKey = "general.license" + fileTypeKey = "general.file_type" + + controlVectorModelHintKey = "controlvector.model_hint" + ) + + gm.FileType = _GGUFFileTypeCount + + m, _ := gf.Header.MetadataKV.Index([]string{ + typeKey, + architectureKey, + quantizationKey, + alignmentKey, + nameKey, + authorKey, + urlKey, + descriptionKey, + licenseKey, + fileTypeKey, + controlVectorModelHintKey, + }) + + if v, ok := m[typeKey]; ok { + gm.Type = v.ValueString() + } else if _, ok = m[controlVectorModelHintKey]; ok { + gm.Type = "adapter" + } else { + gm.Type = "model" + } + if v, ok := m[controlVectorModelHintKey]; ok { + gm.Architecture = v.ValueString() + } else if v, ok = m[architectureKey]; ok { + gm.Architecture = v.ValueString() + if gm.Architecture == "clip" { + gm.Type = "projector" + } + } else { + if gf.TensorInfos.Match(regexp.MustCompile(`^model\.diffusion_model\..*`)) || + gf.TensorInfos.Match(regexp.MustCompile(`^double_blocks\..*`)) { + gm.Architecture = "diffusion" + } else { + gm.Architecture = "llama" + } + } + if v, ok := m[quantizationKey]; ok { + gm.QuantizationVersion = ValueNumeric[uint32](v) + } + if v, ok := m[alignmentKey]; ok { + gm.Alignment = ValueNumeric[uint32](v) + } else { + gm.Alignment = 32 + } + if v, ok := m[nameKey]; ok { + gm.Name = v.ValueString() + } + if v, ok := m[authorKey]; ok { + gm.Author = v.ValueString() + } + if v, ok := m[urlKey]; ok { + gm.URL = v.ValueString() + } + if v, ok := m[descriptionKey]; ok { + gm.Description = v.ValueString() + } + if v, ok := m[licenseKey]; ok { + gm.License = v.ValueString() + } + if v, ok := m[fileTypeKey]; ok { + gm.FileType = GGUFFileType(ValueNumeric[uint32](v)) + } + + if gm.FileType >= _GGUFFileTypeCount { + gm.FileType = gf.guessFileType(gm.Architecture) + } + + gm.LittleEndian = gf.Header.Version < GGUFVersionV3 || gf.Header.Magic == GGUFMagicGGUFLe + gm.FileSize = gf.Size + gm.Size = gf.ModelSize + gm.Parameters = gf.ModelParameters + gm.BitsPerWeight = gf.ModelBitsPerWeight + + return gm +} + +// GGMLType returns the GGMLType of the GGUFFileType, +// which is inspired by +// https://github.com/ggerganov/ggml/blob/a10a8b880c059b3b29356eb9a9f8df72f03cdb6a/src/ggml.c#L2730-L2763. +func (t GGUFFileType) GGMLType() GGMLType { + switch t { + case GGUFFileTypeAllF32: + return GGMLTypeF32 + case GGUFFileTypeMostlyF16: + return GGMLTypeF16 + case GGUFFileTypeMostlyQ4_0: + return GGMLTypeQ4_0 + case GGUFFileTypeMostlyQ4_1: + return GGMLTypeQ4_1 + case GGUFFileTypeMostlyQ4_2: + return GGMLTypeQ4_2 + case GGUFFileTypeMostlyQ4_3: + return GGMLTypeQ4_3 + case GGUFFileTypeMostlyQ8_0: + return GGMLTypeQ8_0 + case GGUFFileTypeMostlyQ5_0: + return GGMLTypeQ5_0 + case GGUFFileTypeMostlyQ5_1: + return GGMLTypeQ5_1 + case GGUFFileTypeMostlyQ2_K: + return GGMLTypeQ2_K + case GGUFFileTypeMostlyQ3_K: + return GGMLTypeQ3_K + case GGUFFileTypeMostlyQ4_K: + return GGMLTypeQ4_K + case GGUFFileTypeMostlyQ5_K: + return GGMLTypeQ5_K + case GGUFFileTypeMostlyQ6_K: + return GGMLTypeQ6_K + case GGUFFileTypeMostlyIQ2_XXS: + return GGMLTypeIQ2_XXS + case GGUFFileTypeMostlyIQ2_XS: + return GGMLTypeIQ2_XS + case GGUFFileTypeMostlyIQ3_XXS: + return GGMLTypeIQ3_XXS + case GGUFFileTypeMostlyIQ1_S: + return GGMLTypeIQ1_S + case GGUFFileTypeMostlyIQ4_NL: + return GGMLTypeIQ4_NL + case GGUFFileTypeMostlyIQ3_S: + return GGMLTypeIQ3_S + case GGUFFileTypeMostlyIQ2_S: + return GGMLTypeIQ2_S + case GGUFFileTypeMostlyIQ4_XS: + return GGMLTypeIQ4_XS + case GGUFFileTypeMostlyIQ1_M: + return GGMLTypeIQ1_M + case GGUFFileTypeMostlyBF16: + return GGMLTypeBF16 + case GGUFFileTypeMostlyQ4_0_4_4: + return GGMLTypeQ4_0_4_4 + case GGUFFileTypeMostlyQ4_0_4_8: + return GGMLTypeQ4_0_4_8 + case GGUFFileTypeMostlyQ4_0_8_8: + return GGMLTypeQ4_0_8_8 + case GGUFFileTypeMostlyTQ1_0: + return GGMLTypeTQ1_0 + case GGUFFileTypeMostlyTQ2_0: + return GGMLTypeTQ2_0 + case GGUFFileTypeMostlyIQ4_NL_4_4: + return GGMLTypeIQ4_NL_4_4 + case GGUFFileTypeMostlyIQ4_NL_4_8: + return GGMLTypeIQ4_NL_4_8 + case GGUFFileTypeMostlyIQ4_NL_8_8: + return GGMLTypeIQ4_NL_8_8 + default: + } + return _GGMLTypeCount +} + +// guessFileType guesses the GGUF file type by +// statistically analyzing the tensor types, +// which is inspired by +// https://huggingface.co/TheBloke/Llama-2-13B-chat-GGML#provided-files. +func (gf *GGUFFile) guessFileType(arch string) GGUFFileType { + if len(gf.TensorInfos) == 0 { + return _GGUFFileTypeCount + } + + // Count. + cm := make(map[GGMLType]int) + for i := range gf.TensorInfos { + switch { + case arch != "diffusion" && !strings.HasPrefix(gf.TensorInfos[i].Name, "blk."): + continue + case arch == "diffusion" && !strings.HasSuffix(gf.TensorInfos[i].Name, ".weight"): + continue + } + cm[gf.TensorInfos[i].Type]++ + } + + return GetFileType(cm) +} + +// GetFileType returns the GGUFFileType represented the mostly GGMLType of the given tensors counter. +// +// The input `cm` is a map of GGMLType to the count of tensors of that type. +func GetFileType(cm map[GGMLType]int) GGUFFileType { + if len(cm) == 0 { + return _GGUFFileTypeCount + } + + // Sort. + ts := maps.Keys(cm) + sort.Slice(ts, func(i, j int) bool { + return cm[ts[i]] > cm[ts[j]] + }) + + // Guess. + if ts[0] == GGMLTypeF32 { + if len(ts) == 1 { + return GGUFFileTypeAllF32 + } + ts[0] = ts[1] + } + switch ts[0] { + case GGMLTypeF16: + return GGUFFileTypeMostlyF16 + case GGMLTypeQ4_0: + return GGUFFileTypeMostlyQ4_0 + case GGMLTypeQ4_1: + return GGUFFileTypeMostlyQ4_1 + case GGMLTypeQ4_2: + return GGUFFileTypeMostlyQ4_2 + case GGMLTypeQ4_3: + return GGUFFileTypeMostlyQ4_3 + case GGMLTypeQ5_0: + return GGUFFileTypeMostlyQ5_0 + case GGMLTypeQ5_1: + return GGUFFileTypeMostlyQ5_1 + case GGMLTypeQ8_0: + return GGUFFileTypeMostlyQ8_0 + case GGMLTypeQ2_K: + return GGUFFileTypeMostlyQ2_K + case GGMLTypeQ3_K: + switch ts[1] { + case GGMLTypeQ4_K: // Legacy, Q3_K_M. + return GGUFFileTypeMostlyQ4_K + case GGMLTypeQ5_K: // Legacy, Q3_K_L. + return GGUFFileTypeMostlyQ5_K + default: // Legacy. Q3_K_S + return GGUFFileTypeMostlyQ3_K + } + case GGMLTypeQ4_K: + if len(ts) > 2 && ts[2] == GGMLTypeQ6_K { // Legacy, Q4_K_M. + return GGUFFileTypeMostlyIQ2_XXS + } + return GGUFFileTypeMostlyQ6_K // Legacy. Q4_K_S + case GGMLTypeQ5_K: + if len(ts) > 2 && ts[2] == GGMLTypeQ6_K { // Legacy, Q5_K_M. + return GGUFFileTypeMostlyIQ3_XXS + } + return GGUFFileTypeMostlyIQ2_XS // Legacy. Q5_K_S + case GGMLTypeQ6_K: + return GGUFFileTypeMostlyIQ1_S // Legacy. Q6_K + case GGMLTypeIQ2_XXS: + return GGUFFileTypeMostlyIQ2_XXS + case GGMLTypeIQ2_XS: + return GGUFFileTypeMostlyIQ2_XS + case GGMLTypeIQ3_XXS: + return GGUFFileTypeMostlyIQ3_XXS + case GGMLTypeIQ1_S: + return GGUFFileTypeMostlyIQ1_S + case GGMLTypeIQ4_NL: + return GGUFFileTypeMostlyIQ4_NL + case GGMLTypeIQ3_S: + return GGUFFileTypeMostlyIQ3_S + case GGMLTypeIQ2_S: + return GGUFFileTypeMostlyIQ2_S + case GGMLTypeIQ4_XS: + return GGUFFileTypeMostlyIQ4_XS + case GGMLTypeIQ1_M: + return GGUFFileTypeMostlyIQ1_M + case GGMLTypeBF16: + return GGUFFileTypeMostlyBF16 + case GGMLTypeQ4_0_4_4: + return GGUFFileTypeMostlyQ4_0_4_4 + case GGMLTypeQ4_0_4_8: + return GGUFFileTypeMostlyQ4_0_4_8 + case GGMLTypeQ4_0_8_8: + return GGUFFileTypeMostlyQ4_0_8_8 + case GGMLTypeTQ1_0: + return GGUFFileTypeMostlyTQ1_0 + case GGMLTypeTQ2_0: + return GGUFFileTypeMostlyTQ2_0 + case GGMLTypeIQ4_NL_4_4: + return GGUFFileTypeMostlyIQ4_NL_4_4 + case GGMLTypeIQ4_NL_4_8: + return GGUFFileTypeMostlyIQ4_NL_4_8 + case GGMLTypeIQ4_NL_8_8: + return GGUFFileTypeMostlyIQ4_NL_8_8 + default: + } + return _GGUFFileTypeCount +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_option.go b/vendor/github.com/gpustack/gguf-parser-go/file_option.go new file mode 100644 index 00000000..ca963724 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_option.go @@ -0,0 +1,158 @@ +package gguf_parser + +import ( + "net/url" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/gpustack/gguf-parser-go/util/osx" +) + +type ( + _GGUFReadOptions struct { + Debug bool + SkipLargeMetadata bool + + // Local. + MMap bool + + // Remote. + BearerAuthToken string + ProxyURL *url.URL + SkipProxy bool + SkipTLSVerification bool + SkipDNSCache bool + BufferSize int + SkipRangeDownloadDetection bool + CachePath string + CacheExpiration time.Duration + } + + // GGUFReadOption is the option for reading the file. + GGUFReadOption func(o *_GGUFReadOptions) +) + +// UseDebug uses debug mode to read the file. +func UseDebug() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.Debug = true + } +} + +// SkipLargeMetadata skips reading large GGUFMetadataKV items, +// which are not necessary for most cases. +func SkipLargeMetadata() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.SkipLargeMetadata = true + } +} + +// UseMMap uses mmap to read the local file. +func UseMMap() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.MMap = true + } +} + +// UseBearerAuth uses the given token as a bearer auth when reading from remote. +func UseBearerAuth(token string) GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.BearerAuthToken = token + } +} + +// UseProxy uses the given url as a proxy when reading from remote. +func UseProxy(url *url.URL) GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.ProxyURL = url + } +} + +// SkipProxy skips the proxy when reading from remote. +func SkipProxy() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.SkipProxy = true + } +} + +// SkipTLSVerification skips the TLS verification when reading from remote. +func SkipTLSVerification() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.SkipTLSVerification = true + } +} + +// SkipDNSCache skips the DNS cache when reading from remote. +func SkipDNSCache() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.SkipDNSCache = true + } +} + +// UseBufferSize sets the buffer size when reading from remote. +func UseBufferSize(size int) GGUFReadOption { + const minSize = 32 * 1024 + if size < minSize { + size = minSize + } + return func(o *_GGUFReadOptions) { + o.BufferSize = size + } +} + +// SkipRangeDownloadDetection skips the range download detection when reading from remote. +func SkipRangeDownloadDetection() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.SkipRangeDownloadDetection = true + } +} + +// UseCache caches the remote reading result. +func UseCache() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.CachePath = DefaultCachePath() + o.CacheExpiration = 24 * time.Hour + } +} + +// SkipCache skips the cache when reading from remote. +func SkipCache() GGUFReadOption { + return func(o *_GGUFReadOptions) { + o.CachePath = "" + o.CacheExpiration = 0 + } +} + +// DefaultCachePath returns the default cache path. +func DefaultCachePath() string { + cd := filepath.Join(osx.UserHomeDir(), ".cache") + if runtime.GOOS == "windows" { + cd = osx.Getenv("APPDATA", cd) + } + return filepath.Join(cd, "gguf-parser") +} + +// UseCachePath uses the given path to cache the remote reading result. +func UseCachePath(path string) GGUFReadOption { + path = strings.TrimSpace(filepath.Clean(osx.InlineTilde(path))) + return func(o *_GGUFReadOptions) { + if path == "" { + return + } + o.CachePath = path + } +} + +// UseCacheExpiration uses the given expiration to cache the remote reading result. +// +// Disable cache expiration by setting it to 0. +func UseCacheExpiration(expiration time.Duration) GGUFReadOption { + if expiration < 0 { + expiration = 0 + } + return func(o *_GGUFReadOptions) { + o.CacheExpiration = expiration + } +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/file_tokenizer.go b/vendor/github.com/gpustack/gguf-parser-go/file_tokenizer.go new file mode 100644 index 00000000..311b1c4d --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/file_tokenizer.go @@ -0,0 +1,129 @@ +package gguf_parser + +// GGUFTokenizer represents the tokenizer metadata of a GGUF file. +type GGUFTokenizer struct { + /* Basic */ + + // Model is the model of the tokenizer. + Model string `json:"model"` + // TokensLength is the size of tokens. + TokensLength uint64 `json:"tokensLength"` + // MergeLength is the size of merges. + MergesLength uint64 `json:"mergesLength"` + // AddedTokensLength is the size of added tokens after training. + AddedTokensLength uint64 `json:"addedTokenLength"` + // BOSTokenID is the ID of the beginning of sentence token. + // + // Use -1 if the token is not found. + BOSTokenID int64 `json:"bosTokenID"` + // EOSTokenID is the ID of the end of sentence token. + // + // Use -1 if the token is not found. + EOSTokenID int64 `json:"eosTokenID"` + // EOTTokenID is the ID of the end of text token. + // + // Use -1 if the token is not found. + EOTTokenID int64 `json:"eotTokenID"` + // EOMTokenID is the ID of the end of message token. + // + // Use -1 if the token is not found. + EOMTokenID int64 `json:"eomTokenID"` + // UnknownTokenID is the ID of the unknown token. + // + // Use -1 if the token is not found. + UnknownTokenID int64 `json:"unknownTokenID"` + // SeparatorTokenID is the ID of the separator token. + // + // Use -1 if the token is not found. + SeparatorTokenID int64 `json:"separatorTokenID"` + // PaddingTokenID is the ID of the padding token. + // + // Use -1 if the token is not found. + PaddingTokenID int64 `json:"paddingTokenID"` + + /* Appendix */ + + // TokenSize is the size of tokens in bytes. + TokensSize int64 `json:"tokensSize"` + // MergesSize is the size of merges in bytes. + MergesSize int64 `json:"mergesSize"` +} + +// Tokenizer returns the tokenizer metadata of a GGUF file. +func (gf *GGUFFile) Tokenizer() (gt GGUFTokenizer) { + const ( + modelKey = "tokenizer.ggml.model" + tokensKey = "tokenizer.ggml.tokens" + mergesKey = "tokenizer.ggml.merges" + addedTokensKey = "tokenizer.ggml.added_tokens" + bosTokenIDKey = "tokenizer.ggml.bos_token_id" + eosTokenIDKey = "tokenizer.ggml.eos_token_id" + eotTokenIDKey = "tokenizer.ggml.eot_token_id" + eomTokenIDKey = "tokenizer.ggml.eom_token_id" + unknownTokenIDKey = "tokenizer.ggml.unknown_token_id" + separatorTokenIDKey = "tokenizer.ggml.separator_token_id" + paddingTokenIDKey = "tokenizer.ggml.padding_token_id" + ) + + m, _ := gf.Header.MetadataKV.Index([]string{ + modelKey, + tokensKey, + mergesKey, + addedTokensKey, + bosTokenIDKey, + eosTokenIDKey, + eotTokenIDKey, + eomTokenIDKey, + unknownTokenIDKey, + separatorTokenIDKey, + paddingTokenIDKey, + }) + + gt.BOSTokenID = -1 + gt.EOSTokenID = -1 + gt.EOTTokenID = -1 + gt.EOMTokenID = -1 + gt.UnknownTokenID = -1 + gt.SeparatorTokenID = -1 + gt.PaddingTokenID = -1 + + if v, ok := m[modelKey]; ok { + gt.Model = v.ValueString() + } + if v, ok := m[tokensKey]; ok { + arr := v.ValueArray() + gt.TokensLength = arr.Len + gt.TokensSize = arr.Size + } + if v, ok := m[mergesKey]; ok { + arr := v.ValueArray() + gt.MergesLength = arr.Len + gt.MergesSize = arr.Size + } + if v, ok := m[addedTokensKey]; ok { + gt.AddedTokensLength = v.ValueArray().Len + } + if v, ok := m[bosTokenIDKey]; ok { + gt.BOSTokenID = ValueNumeric[int64](v) + } + if v, ok := m[eosTokenIDKey]; ok { + gt.EOSTokenID = ValueNumeric[int64](v) + } + if v, ok := m[eotTokenIDKey]; ok { + gt.EOTTokenID = ValueNumeric[int64](v) + } + if v, ok := m[eomTokenIDKey]; ok { + gt.EOMTokenID = ValueNumeric[int64](v) + } + if v, ok := m[unknownTokenIDKey]; ok { + gt.UnknownTokenID = ValueNumeric[int64](v) + } + if v, ok := m[separatorTokenIDKey]; ok { + gt.SeparatorTokenID = ValueNumeric[int64](v) + } + if v, ok := m[paddingTokenIDKey]; ok { + gt.PaddingTokenID = ValueNumeric[int64](v) + } + + return gt +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/filename.go b/vendor/github.com/gpustack/gguf-parser-go/filename.go new file mode 100644 index 00000000..3f5012d9 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/filename.go @@ -0,0 +1,176 @@ +package gguf_parser + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/gpustack/gguf-parser-go/util/funcx" + "github.com/gpustack/gguf-parser-go/util/ptr" +) + +// GGUFFilename represents a GGUF filename, +// see https://github.com/ggerganov/ggml/blob/master/docs/gguf.md#gguf-naming-convention. +type GGUFFilename struct { + BaseName string `json:"baseName"` + SizeLabel string `json:"sizeLabel"` + FineTune string `json:"fineTune"` + Version string `json:"version"` + Encoding string `json:"encoding"` + Type string `json:"type"` + Shard *int `json:"shard,omitempty"` + ShardTotal *int `json:"shardTotal,omitempty"` +} + +var GGUFFilenameRegex = regexp.MustCompile(`^(?P[A-Za-z\s][A-Za-z0-9._\s]*(?:(?:-(?:(?:[A-Za-z\s][A-Za-z0-9._\s]*)|(?:[0-9._\s]*)))*))-(?:(?P(?:\d+x)?(?:\d+\.)?\d+[A-Za-z](?:-[A-Za-z]+(\d+\.)?\d+[A-Za-z]+)?)(?:-(?P[A-Za-z][A-Za-z0-9\s_-]+[A-Za-z](?i:[^BFKIQ])))?)?(?:-(?P[vV]\d+(?:\.\d+)*))?(?i:-(?P(BF16|F32|F16|([KI]?Q[0-9][A-Z0-9_]*))))?(?:-(?PLoRA|vocab))?(?:-(?P\d{5})-of-(?P\d{5}))?\.gguf$`) // nolint:lll + +// ParseGGUFFilename parses the given GGUF filename string, +// and returns the GGUFFilename, or nil if the filename is invalid. +func ParseGGUFFilename(name string) *GGUFFilename { + n := name + if !strings.HasSuffix(n, ".gguf") { + n += ".gguf" + } + + m := make(map[string]string) + { + r := GGUFFilenameRegex.FindStringSubmatch(n) + for i, ne := range GGUFFilenameRegex.SubexpNames() { + if i != 0 && i <= len(r) { + m[ne] = r[i] + } + } + } + if m["BaseName"] == "" { + return nil + } + + var gn GGUFFilename + gn.BaseName = strings.ReplaceAll(m["BaseName"], "-", " ") + gn.SizeLabel = m["SizeLabel"] + gn.FineTune = m["FineTune"] + gn.Version = m["Version"] + gn.Encoding = m["Encoding"] + gn.Type = m["Type"] + if v := m["Shard"]; v != "" { + gn.Shard = ptr.To(parseInt(v)) + } + if v := m["ShardTotal"]; v != "" { + gn.ShardTotal = ptr.To(parseInt(v)) + } + return &gn +} + +func (gn GGUFFilename) String() string { + if gn.BaseName == "" { + return "" + } + + var sb strings.Builder + sb.WriteString(strings.ReplaceAll(gn.BaseName, " ", "-")) + if gn.SizeLabel != "" { + sb.WriteString("-") + sb.WriteString(gn.SizeLabel) + } + if gn.FineTune != "" { + sb.WriteString("-") + sb.WriteString(gn.FineTune) + } + if gn.Version != "" { + sb.WriteString("-") + sb.WriteString(gn.Version) + } + if gn.Encoding != "" { + sb.WriteString("-") + sb.WriteString(gn.Encoding) + } + if gn.Type != "" { + sb.WriteString("-") + sb.WriteString(gn.Type) + } + if m, n := ptr.Deref(gn.Shard, 0), ptr.Deref(gn.ShardTotal, 0); m > 0 && n > 0 { + sb.WriteString("-") + sb.WriteString(fmt.Sprintf("%05d", m)) + sb.WriteString("-of-") + sb.WriteString(fmt.Sprintf("%05d", n)) + } + sb.WriteString(".gguf") + return sb.String() +} + +// IsShard returns true if the GGUF filename is a shard. +func (gn GGUFFilename) IsShard() bool { + return ptr.Deref(gn.Shard, 0) > 0 && ptr.Deref(gn.ShardTotal, 0) > 0 +} + +var ShardGGUFFilenameRegex = regexp.MustCompile(`^(?P.*)-(?:(?P\d{5})-of-(?P\d{5}))\.gguf$`) + +// IsShardGGUFFilename returns true if the given filename is a shard GGUF filename. +func IsShardGGUFFilename(name string) bool { + n := name + if !strings.HasSuffix(n, ".gguf") { + n += ".gguf" + } + + m := make(map[string]string) + { + r := ShardGGUFFilenameRegex.FindStringSubmatch(n) + for i, ne := range ShardGGUFFilenameRegex.SubexpNames() { + if i != 0 && i <= len(r) { + m[ne] = r[i] + } + } + } + + var shard, shardTotal int + if v := m["Shard"]; v != "" { + shard = parseInt(v) + } + if v := m["ShardTotal"]; v != "" { + shardTotal = parseInt(v) + } + return shard > 0 && shardTotal > 0 +} + +// CompleteShardGGUFFilename returns the list of shard GGUF filenames that are related to the given shard GGUF filename. +// +// Only available if the given filename is a shard GGUF filename. +func CompleteShardGGUFFilename(name string) []string { + n := name + if !strings.HasSuffix(n, ".gguf") { + n += ".gguf" + } + + m := make(map[string]string) + { + r := ShardGGUFFilenameRegex.FindStringSubmatch(n) + for i, ne := range ShardGGUFFilenameRegex.SubexpNames() { + if i != 0 && i <= len(r) { + m[ne] = r[i] + } + } + } + + var shard, shardTotal int + if v := m["Shard"]; v != "" { + shard = parseInt(v) + } + if v := m["ShardTotal"]; v != "" { + shardTotal = parseInt(v) + } + + if shard <= 0 || shardTotal <= 0 { + return nil + } + + names := make([]string, 0, shardTotal) + for i := 1; i <= shardTotal; i++ { + names = append(names, fmt.Sprintf("%s-%05d-of-%05d.gguf", m["Prefix"], i, shardTotal)) + } + return names +} + +func parseInt(v string) int { + return int(funcx.MustNoError(strconv.ParseInt(v, 10, 64))) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/gen.go b/vendor/github.com/gpustack/gguf-parser-go/gen.go new file mode 100644 index 00000000..6f8ff36b --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/gen.go @@ -0,0 +1,3 @@ +//go:generate go generate -tags stringer gen.stringer.go +//go:generate go generate -tags regression gen.regression.go +package gguf_parser diff --git a/vendor/github.com/gpustack/gguf-parser-go/gen.regression.go b/vendor/github.com/gpustack/gguf-parser-go/gen.regression.go new file mode 100644 index 00000000..2024a4ed --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/gen.regression.go @@ -0,0 +1,495 @@ +//go:build regression + +//go:generate go run -tags regression gen.regression.go +package main + +import ( + "fmt" + "strconv" + "math" + "os" + "text/template" + "bytes" + "go/format" + + "gonum.org/v1/gonum/mat" + "golang.org/x/exp/maps" + "sort" +) + +type LinearRegression struct { + Intercept float64 + Slope float64 +} + +func (lr *LinearRegression) Fit(xs, ys []float64) { + if len(xs) != len(ys) { + panic("length of xs and ys must be the same") + } + + var sX, sY, sXY, sXX float64 + for i := 0; i < len(xs); i++ { + sX += xs[i] + sY += ys[i] + sXY += xs[i] * ys[i] + sXX += xs[i] * xs[i] + } + + n := float64(len(xs)) + d := n*sXX - sX*sX + if d == 0 { + d = 1 + } + + lr.Slope = (n*sXY - sX*sY) / d + lr.Intercept = (sY*sXX - sX*sXY) / d +} + +func (lr *LinearRegression) Predict(x float64) (y float64) { + return lr.Intercept + lr.Slope*x +} + +type PolynomialRegression struct { + Degree int + Coefficients []float64 +} + +func (pr *PolynomialRegression) Fit(xs, ys []float64) { + samples := len(xs) + feats := pr.Degree + 1 + + feat := mat.NewDense(samples, feats, nil) + { + for i := 0; i < samples; i++ { + for j := 0; j < feats; j++ { + feat.Set(i, j, math.Pow(xs[i], float64(j))) + } + } + var qr mat.QR + qr.Factorize(feat) + } + yVec := mat.NewVecDense(samples, ys) + + var coef mat.VecDense + if err := coef.SolveVec(feat, yVec); err != nil { + panic("failed to solve") + } + + pr.Coefficients = coef.RawVector().Data +} + +func (pr *PolynomialRegression) Predict(x float64) (y float64) { + y = 0 + for i := 0; i < pr.Degree+1; i++ { + y += pr.Coefficients[i] * math.Pow(x, float64(i)) + } + return +} + +func DiffusionModelMemoryUsageRegression(output string) { + type Regression struct { + Name string + LinearRegression *LinearRegression + PolynomialRegression *PolynomialRegression + } + + const tmplStr = ` +package gguf_parser + +import "math" + +{{ range . -}} +// {{ .Name }} returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func {{ .Name }}(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{ {{ range $i, $c := .PolynomialRegression.Coefficients }}{{ if eq $i 0 }}{{ printf "%.4f" $c }}{{ else }}{{ printf "%.10f" $c }}{{ end }}, {{ end }} } + degree := {{ .PolynomialRegression.Degree }} + x := float64(width * height) + + {{ if .LinearRegression -}} + if flashAttention { + coefficients = []float64{ {{ printf "%.5f" .LinearRegression.Intercept }}, {{ printf "%.10f" .LinearRegression.Slope }} } + degree = 1 + } + {{- end }} + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} + +{{ end }} + +` + ts := []struct { + n string + x2y map[float64]float64 + c map[float64]float64 + fax2y map[float64]float64 + fac map[float64]float64 + }{ + { + n: "GuessSD1DiffusionModelMemoryUsage", + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 49.57 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 559.90 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 8360.93 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 18681.62 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 25377.96 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 41842.65 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 77333.77 MB(VRAM) // 1792*1792 + x2y: map[float64]float64{ + 256 * 256: 49.57, + 512 * 512: 559.90, + 1024 * 1024: 8360.93, + 1024 * 1536: 18681.62, + 1024 * 1792: 25377.96, + 1536 * 1536: 41842.65, + 1792 * 1792: 77333.77, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 56879.17 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 100924.37 MB(VRAM) // 1792*2048 + c: map[float64]float64{ + 1536 * 1792: 56879.17, + 1792 * 2048: 100924.37, + }, + }, + { + n: "GuessSD2DiffusionModelMemoryUsage", + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 37.65 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 367.98 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 830.86 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 11769.69 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 15970.04 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 26290.73 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 48521.84 MB(VRAM) // 1792*1792 + x2y: map[float64]float64{ + 256 * 256: 37.65, + 512 * 512: 367.98, + 1024 * 1024: 830.86, + 1024 * 1536: 11769.69, + 1024 * 1792: 15970.04, + 1536 * 1536: 26290.73, + 1792 * 1792: 48521.84, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 35711.24 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 63292.44 MB(VRAM) // 1792*2048 + c: map[float64]float64{ + 1536 * 1792: 35711.24, + 1792 * 2048: 63292.44, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 34.52 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 130.48 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 519.01 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 774.69 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 902.54 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1158.23 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1573.72 MB(VRAM) // 1792*1792 + fax2y: map[float64]float64{ + 256 * 256: 34.52, + 512 * 512: 130.48, + 1024 * 1024: 519.01, + 1024 * 1536: 774.69, + 1024 * 1792: 902.54, + 1536 * 1536: 1158.23, + 1792 * 1792: 1573.72, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1349.99 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1797.44 MB(VRAM) // 1792*2048 + fac: map[float64]float64{ + 1536 * 1792: 1349.99, + 1792 * 2048: 1797.44, + }, + }, + { + n: "GuessSDXLDiffusionModelMemoryUsage", + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 60.76 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 132.05 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 830.86 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1701.55 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 2256.90 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 3607.58 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 6484.95 MB(VRAM) // 1792*1792 + x2y: map[float64]float64{ + 256 * 256: 60.76, + 512 * 512: 132.05, + 1024 * 1024: 830.86, + 1024 * 1536: 1701.55, + 1024 * 1792: 2256.90, + 1536 * 1536: 3607.58, + 1792 * 1792: 6484.95, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 4830.60 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 8384.30 MB(VRAM) // 1792*2048 + c: map[float64]float64{ + 1536 * 1792: 4830.60, + 1792 * 2048: 8384.30, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 60.13 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 132.05 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 440.86 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 726.55 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 874.40 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1110.08 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1554.33 MB(VRAM) // 1792*1792 + fax2y: map[float64]float64{ + 256 * 256: 60.13, + 512 * 512: 132.05, + 1024 * 1024: 440.86, + 1024 * 1536: 726.55, + 1024 * 1792: 874.40, + 1536 * 1536: 1110.08, + 1792 * 1792: 1554.33, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1339.35 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1769.30 MB(VRAM) // 1792*2048 + fac: map[float64]float64{ + 1536 * 1792: 1339.35, + 1792 * 2048: 1769.30, + }, + }, + { + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 44.57 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 154.40 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 968.43 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 2013.12 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 2679.46 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 4300.15 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 7752.77 MB(VRAM) // 1792*1792 + n: "GuessSDXLRefinerDiffusionModelMemoryUsage", + x2y: map[float64]float64{ + 256 * 256: 44.57, + 512 * 512: 154.40, + 1024 * 1024: 968.43, + 1024 * 1536: 2013.12, + 1024 * 1792: 2679.46, + 1536 * 1536: 4300.15, + 1792 * 1792: 7752.77, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 5767.67 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 10031.87 MB(VRAM) // 1792*2048 + c: map[float64]float64{ + 1536 * 1792: 5767.67, + 1792 * 2048: 10031.87, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 44.57 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 154.40 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 596.43 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 915.12 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1062.46 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1357.15 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1836.02 MB(VRAM) // 1792*1792 + fax2y: map[float64]float64{ + 256 * 256: 44.57, + 512 * 512: 154.40, + 1024 * 1024: 596.43, + 1024 * 1536: 915.12, + 1024 * 1792: 1062.46, + 1536 * 1536: 1357.15, + 1792 * 1792: 1836.02, + }, + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 1578.17 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - unet compute buffer size: 2014.02 MB(VRAM) // 1792*2048 + fac: map[float64]float64{ + 1536 * 1792: 1578.17, + 1792 * 2048: 2014.02, + }, + }, + { + n: "GuessSD3MediumDiffusionModelMemoryUsage", + // [DEBUG] ggml_extend.hpp:1034 - mmdit compute buffer size: 37.09 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1034 - mmdit compute buffer size: 169.64 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1034 - mmdit compute buffer size: 1786.11 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1034 - mmdit compute buffer size: 3824.36 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1034 - mmdit compute buffer size: 5131.48 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1034 - mmdit compute buffer size: 8319.03 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1034 - mmdit compute buffer size: 15141.18 MB(VRAM) // 1792*1792 + x2y: map[float64]float64{ + 256 * 256: 37.09, + 512 * 512: 169.64, + 1024 * 1024: 1786.11, + 1024 * 1536: 3824.36, + 1024 * 1792: 5131.48, + 1536 * 1536: 8319.03, + 1792 * 1792: 15141.18, + }, + // [DEBUG] ggml_extend.hpp:1034 - mmdit compute buffer size: 11215.71 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 19654.65 MB(VRAM) // 1792*2048 + c: map[float64]float64{ + 1536 * 1792: 11215.71, + 1792 * 2048: 19654.65, + }, + }, + { + n: "GuessSD35MediumDiffusionModelMemoryUsage", + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 41.48 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 181.64 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 1834.11 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 3896.36 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 5215.48 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 8427.03 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 15288.18 MiB(VRAM) // 1792*1792 + x2y: map[float64]float64{ + 256 * 256: 41.48, + 512 * 512: 181.64, + 1024 * 1024: 1834.11, + 1024 * 1536: 3896.36, + 1024 * 1792: 5215.48, + 1536 * 1536: 8427.03, + 1792 * 1792: 15288.18, + }, + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 11341.71 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 19822.65 MB(VRAM) // 1792*2048 + c: map[float64]float64{ + 1536 * 1792: 11341.71, + 1792 * 2048: 19822.65, + }, + }, + { + n: "GuessSD35LargeDiffusionModelMemoryUsage", + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 57.27 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 276.54 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 2865.44 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 6109.95 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 8188.92 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 13258.86 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 24091.01 MiB(VRAM) // 1792*1792 + x2y: map[float64]float64{ + 256 * 256: 57.27, + 512 * 512: 276.54, + 1024 * 1024: 2865.44, + 1024 * 1536: 6109.95, + 1024 * 1792: 8188.92, + 1536 * 1536: 13258.86, + 1792 * 1792: 24091.01, + }, + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 17859.31 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - mmdit compute buffer size: 31253.70 MB(VRAM) // 1792*2048 + c: map[float64]float64{ + 1536 * 1792: 17859.31, + 1792 * 2048: 31253.70, + }, + }, + { + n: "GuessFLUXDiffusionModelMemoryUsage", + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 103.35 MB(VRAM) // 256*256 + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 398.05 MB(VRAM) // 512*512 + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 2576.18 MB(VRAM) // 1024*1024 + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 4978.31 MB(VRAM) // 1024*1536 + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 6467.37 MB(VRAM) // 1024*1792 + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 10021.49 MB(VRAM) // 1536*1536 + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 17434.95 MB(VRAM) // 1792*1792 + x2y: map[float64]float64{ + 256 * 256: 103.35, + 512 * 512: 398.05, + 1024 * 1024: 2576.18, + 1024 * 1536: 4978.31, + 1024 * 1792: 6467.37, + 1536 * 1536: 10021.49, + 1792 * 1792: 17434.95, + }, + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 13191.09 MB(VRAM) // 1536*1792 + // [DEBUG] ggml_extend.hpp:1031 - flux compute buffer size: 22266.81 MB(VRAM) // 1792*2048 + c: map[float64]float64{ + 1536 * 1792: 13191.09, + 1792 * 2048: 22266.81, + }, + }, + } + + rs := make([]Regression, len(ts)) + for i, t := range ts { + rs[i].Name = t.n + } + + fmt.Println("Polynomial Regression For None Flash Attention") + for i, t := range ts { + pr := PolynomialRegression{ + Degree: 2, + } + + xs := maps.Keys(t.x2y) + sort.Float64s(xs) + ys := make([]float64, len(xs)) + for j, x := range xs { + ys[j] = t.x2y[x] * 1024 * 1024 // MB to B + } + pr.Fit(xs, ys) + + for x, y := range t.c { + y_ := pr.Predict(x) / 1024 / 1024 // B to MB + d := (y_ - y) / y * 100 + s := "+" + if d < 0 { + s = "" + } + c := "" + if d > 10 { + c = "?" + } + + fmt.Printf("%50s: y': %10.2f | y: %10.2f | d: %10s%% %s\n", t.n, y_, y, s+strconv.FormatFloat(d, 'f', 6, 64), c) + } + + rs[i].PolynomialRegression = &pr + } + + fmt.Println("Linear Regression For Flash Attention") + for i, t := range ts { + if len(t.fax2y) == 0 { + continue + } + + lr := LinearRegression{} + + xs := maps.Keys(t.fax2y) + sort.Float64s(xs) + ys := make([]float64, len(xs)) + for j, x := range xs { + ys[j] = t.fax2y[x] * 1024 * 1024 // MB to B + } + lr.Fit(xs, ys) + + for x, y := range t.fac { + y_ := lr.Predict(x) / 1024 / 1024 // B to MB + d := (y_ - y) / y * 100 + s := "+" + if d < 0 { + s = "" + } + c := "" + if d > 10 { + c = "?" + } + + fmt.Printf("%50s: y': %10.2f | y: %10.2f | d: %10s%% %s\n", t.n, y_, y, s+strconv.FormatFloat(d, 'f', 6, 64), c) + } + + rs[i].LinearRegression = &lr + } + + var code []byte + { + var ( + buff bytes.Buffer + err error + ) + tmpl := template.Must(template.New("tmpl").Parse(tmplStr)) + if err = tmpl.Execute(&buff, rs); err != nil { + panic(fmt.Errorf("failed to execute template: %w", err)) + } + code, err = format.Source(buff.Bytes()) + if err != nil { + panic(fmt.Errorf("failed to format source: %w", err)) + } + } + + if err := os.WriteFile(output, code, 0644); err != nil { + panic(fmt.Errorf("failed to write file: %w", err)) + } +} + +func main() { + DiffusionModelMemoryUsageRegression("zz_generated.diffusion_model_memory_usage.regression.go") +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/gen.stringer.go b/vendor/github.com/gpustack/gguf-parser-go/gen.stringer.go new file mode 100644 index 00000000..0926dc89 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/gen.stringer.go @@ -0,0 +1,10 @@ +//go:build stringer + +//go:generate go run golang.org/x/tools/cmd/stringer -linecomment -type GGUFMagic -output zz_generated.ggufmagic.stringer.go -trimprefix GGUFMagic +//go:generate go run golang.org/x/tools/cmd/stringer -linecomment -type GGUFVersion -output zz_generated.ggufversion.stringer.go -trimprefix GGUFVersion +//go:generate go run golang.org/x/tools/cmd/stringer -linecomment -type GGUFMetadataValueType -output zz_generated.ggufmetadatavaluetype.stringer.go -trimprefix GGUFMetadataValueType +//go:generate go run golang.org/x/tools/cmd/stringer -linecomment -type GGUFFileType -output zz_generated.gguffiletype.stringer.go -trimprefix GGUFFileType +//go:generate go run golang.org/x/tools/cmd/stringer -linecomment -type GGMLType -output zz_generated.ggmltype.stringer.go -trimprefix GGMLType +package gguf_parser + +import _ "golang.org/x/tools/cmd/stringer" diff --git a/vendor/github.com/gpustack/gguf-parser-go/ggml.go b/vendor/github.com/gpustack/gguf-parser-go/ggml.go new file mode 100644 index 00000000..07146935 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/ggml.go @@ -0,0 +1,233 @@ +package gguf_parser + +import ( + "errors" + "fmt" + "slices" +) + +// Types for GGMLType. +type ( + // GGMLType is a type of GGML tensor, + // see https://github.com/ggerganov/llama.cpp/blob/b34e02348064c2f0cef1f89b44d9bee4eb15b9e7/ggml/include/ggml.h#L363-L401. + GGMLType uint32 + + // GGMLTypeTrait holds the trait of a GGMLType, + // see https://github.com/ggerganov/llama.cpp/blob/b34e02348064c2f0cef1f89b44d9bee4eb15b9e7/ggml/src/ggml.c#L663-L1082. + GGMLTypeTrait struct { + BlockSize uint64 // Original is int, in order to reduce conversion, here we use uint64. + TypeSize uint64 // Original is uint32, in order to reduce conversion, here we use uint64. + Quantized bool + } +) + +// GGMLType constants. +// +// GGMLTypeQ4_2, GGMLTypeQ4_3 are deprecated. +const ( + GGMLTypeF32 GGMLType = iota + GGMLTypeF16 + GGMLTypeQ4_0 + GGMLTypeQ4_1 + GGMLTypeQ4_2 + GGMLTypeQ4_3 + GGMLTypeQ5_0 + GGMLTypeQ5_1 + GGMLTypeQ8_0 + GGMLTypeQ8_1 + GGMLTypeQ2_K + GGMLTypeQ3_K + GGMLTypeQ4_K + GGMLTypeQ5_K + GGMLTypeQ6_K + GGMLTypeQ8_K + GGMLTypeIQ2_XXS + GGMLTypeIQ2_XS + GGMLTypeIQ3_XXS + GGMLTypeIQ1_S + GGMLTypeIQ4_NL + GGMLTypeIQ3_S + GGMLTypeIQ2_S + GGMLTypeIQ4_XS + GGMLTypeI8 + GGMLTypeI16 + GGMLTypeI32 + GGMLTypeI64 + GGMLTypeF64 + GGMLTypeIQ1_M + GGMLTypeBF16 + GGMLTypeQ4_0_4_4 + GGMLTypeQ4_0_4_8 + GGMLTypeQ4_0_8_8 + GGMLTypeTQ1_0 + GGMLTypeTQ2_0 + GGMLTypeIQ4_NL_4_4 + GGMLTypeIQ4_NL_4_8 + GGMLTypeIQ4_NL_8_8 + _GGMLTypeCount // Unknown +) + +// _GGMLTypeTraits is a table of GGMLTypeTrait for GGMLType. +var _GGMLTypeTraits = map[GGMLType]GGMLTypeTrait{ + GGMLTypeF32: {BlockSize: 1, TypeSize: 4}, + GGMLTypeF16: {BlockSize: 1, TypeSize: 2}, + GGMLTypeQ4_0: {BlockSize: 32, TypeSize: 18, Quantized: true}, + GGMLTypeQ4_1: {BlockSize: 32, TypeSize: 20, Quantized: true}, + GGMLTypeQ4_2: {BlockSize: 0, TypeSize: 0}, // Deprecated + GGMLTypeQ4_3: {BlockSize: 0, TypeSize: 0}, // Deprecated + GGMLTypeQ5_0: {BlockSize: 32, TypeSize: 22, Quantized: true}, + GGMLTypeQ5_1: {BlockSize: 32, TypeSize: 24, Quantized: true}, + GGMLTypeQ8_0: {BlockSize: 32, TypeSize: 34, Quantized: true}, + GGMLTypeQ8_1: {BlockSize: 32, TypeSize: 36, Quantized: true}, + GGMLTypeQ2_K: {BlockSize: 256, TypeSize: 84, Quantized: true}, + GGMLTypeQ3_K: {BlockSize: 256, TypeSize: 110, Quantized: true}, + GGMLTypeQ4_K: {BlockSize: 256, TypeSize: 144, Quantized: true}, + GGMLTypeQ5_K: {BlockSize: 256, TypeSize: 176, Quantized: true}, + GGMLTypeQ6_K: {BlockSize: 256, TypeSize: 210, Quantized: true}, + GGMLTypeQ8_K: {BlockSize: 256, TypeSize: 292, Quantized: true}, + GGMLTypeIQ2_XXS: {BlockSize: 256, TypeSize: 66, Quantized: true}, + GGMLTypeIQ2_XS: {BlockSize: 256, TypeSize: 74, Quantized: true}, + GGMLTypeIQ3_XXS: {BlockSize: 256, TypeSize: 98, Quantized: true}, + GGMLTypeIQ1_S: {BlockSize: 256, TypeSize: 50, Quantized: true}, + GGMLTypeIQ4_NL: {BlockSize: 32, TypeSize: 18, Quantized: true}, + GGMLTypeIQ3_S: {BlockSize: 256, TypeSize: 110, Quantized: true}, + GGMLTypeIQ2_S: {BlockSize: 256, TypeSize: 82, Quantized: true}, + GGMLTypeIQ4_XS: {BlockSize: 256, TypeSize: 136, Quantized: true}, + GGMLTypeI8: {BlockSize: 1, TypeSize: 1}, + GGMLTypeI16: {BlockSize: 1, TypeSize: 2}, + GGMLTypeI32: {BlockSize: 1, TypeSize: 4}, + GGMLTypeI64: {BlockSize: 1, TypeSize: 8}, + GGMLTypeF64: {BlockSize: 1, TypeSize: 8}, + GGMLTypeIQ1_M: {BlockSize: 256, TypeSize: 56, Quantized: true}, + GGMLTypeBF16: {BlockSize: 1, TypeSize: 2}, + GGMLTypeQ4_0_4_4: {BlockSize: 32, TypeSize: 18, Quantized: true}, + GGMLTypeQ4_0_4_8: {BlockSize: 32, TypeSize: 18, Quantized: true}, + GGMLTypeQ4_0_8_8: {BlockSize: 32, TypeSize: 18, Quantized: true}, + GGMLTypeTQ1_0: {BlockSize: 256, TypeSize: 54, Quantized: true}, + GGMLTypeTQ2_0: {BlockSize: 256, TypeSize: 66, Quantized: true}, + GGMLTypeIQ4_NL_4_4: {BlockSize: 32, TypeSize: 18, Quantized: true}, + GGMLTypeIQ4_NL_4_8: {BlockSize: 32, TypeSize: 18, Quantized: true}, + GGMLTypeIQ4_NL_8_8: {BlockSize: 32, TypeSize: 18, Quantized: true}, +} + +// Trait returns the GGMLTypeTrait of the GGMLType. +func (t GGMLType) Trait() (GGMLTypeTrait, bool) { + tt, ok := _GGMLTypeTraits[t] + return tt, ok +} + +// IsQuantized returns whether the GGMLType is quantized. +func (t GGMLType) IsQuantized() bool { + tt, ok := t.Trait() + if !ok { + return false + } + return tt.Quantized +} + +// RowSizeOf returns the size of the given dimensions according to the GGMLType's GGMLTypeTrait, +// which is inspired by +// https://github.com/ggerganov/ggml/blob/0cbb7c0e053f5419cfbebb46fbf4d4ed60182cf5/src/ggml.c#L3142-L3145. +// +// The index of the given dimensions means the number of dimension, +// i.e. 0 is the first dimension, 1 is the second dimension, and so on. +// +// The value of the item is the number of elements in the corresponding dimension. +func (t GGMLType) RowSizeOf(dimensions []uint64) uint64 { + if len(dimensions) == 0 { + panic(errors.New("no dimensions")) + } + + tt, ok := t.Trait() + if !ok { + panic(fmt.Errorf("invalid type: %v", t)) + } + + // https://github.com/ggerganov/ggml/blob/a10a8b880c059b3b29356eb9a9f8df72f03cdb6a/src/ggml.c#L2640-L2643 + ds := tt.TypeSize * dimensions[0] / tt.BlockSize // Row size + for i := 1; i < len(dimensions); i++ { + ds *= dimensions[i] + } + return ds +} + +// GGMLMemoryPadding returns the padded size of the given size according to GGML memory padding, +// see https://github.com/ggerganov/ggml/blob/0cbb7c0/include/ggml/ggml.h#L238-L243. +func GGMLMemoryPadding(size uint64) uint64 { + const align = 16 + return GGMLPadding(size, align) +} + +// GGMLPadding returns the padded size of the given size according to given align, +// see https://github.com/ggerganov/ggml/blob/0cbb7c0e053f5419cfbebb46fbf4d4ed60182cf5/include/ggml/ggml.h#L255. +func GGMLPadding(size, align uint64) uint64 { + return (size + align - 1) &^ (align - 1) +} + +// GGML tensor constants. +const ( + // GGMLTensorSize is the size of GGML tensor in bytes, + // see https://github.com/ggerganov/ggml/blob/0cbb7c0e053f5419cfbebb46fbf4d4ed60182cf5/include/ggml/ggml.h#L606. + GGMLTensorSize = 368 + + // GGMLObjectSize is the size of GGML object in bytes, + // see https://github.com/ggerganov/ggml/blob/0cbb7c0e053f5419cfbebb46fbf4d4ed60182cf5/include/ggml/ggml.h#L563. + GGMLObjectSize = 32 +) + +// GGMLTensorOverhead is the overhead of GGML tensor in bytes, +// see https://github.com/ggerganov/ggml/blob/0cbb7c0e053f5419cfbebb46fbf4d4ed60182cf5/src/ggml.c#L2765-L2767. +func GGMLTensorOverhead() uint64 { + return GGMLObjectSize + GGMLTensorSize +} + +// GGML computation graph constants. +const ( + // GGMLComputationGraphSize is the size of GGML computation graph in bytes. + GGMLComputationGraphSize = 80 + + // GGMLComputationGraphNodesMaximum is the maximum nodes of the computation graph, + // see https://github.com/ggerganov/llama.cpp/blob/7672adeec7a79ea271058c63106c142ba84f951a/llama.cpp#L103. + GGMLComputationGraphNodesMaximum = 8192 + + // GGMLComputationGraphNodesDefault is the default nodes of the computation graph, + // see https://github.com/ggerganov/ggml/blob/0cbb7c0e053f5419cfbebb46fbf4d4ed60182cf5/include/ggml/ggml.h#L237. + GGMLComputationGraphNodesDefault = 2048 +) + +// GGMLComputationGraphOverhead is the overhead of GGML graph in bytes, +// see https://github.com/ggerganov/ggml/blob/0cbb7c0e053f5419cfbebb46fbf4d4ed60182cf5/src/ggml.c#L18905-L18917. +func GGMLComputationGraphOverhead(nodes uint64, grads bool) uint64 { + const pointerSize = 8 + + var g uint64 = GGMLComputationGraphSize + g += pointerSize * nodes * 2 + if grads { + g += pointerSize * nodes + } + g += pointerSize * GGMLHashSize(nodes) + + return GGMLObjectSize + GGMLMemoryPadding(g) +} + +// GGMLHashSize returns the size of the hash table for the given base, +// see https://github.com/ggerganov/ggml/blob/0cbb7c0e053f5419cfbebb46fbf4d4ed60182cf5/src/ggml.c#L17698-L17722. +func GGMLHashSize(base uint64) uint64 { + primes := []uint64{ + 2, 3, 5, 11, 17, 37, 67, 131, 257, 521, 1031, + 2053, 4099, 8209, 16411, 32771, 65537, 131101, + 262147, 524309, 1048583, 2097169, 4194319, 8388617, + 16777259, 33554467, 67108879, 134217757, 268435459, + 536870923, 1073741827, 2147483659, + } + i, ok := slices.BinarySearchFunc(primes, base, func(e, t uint64) int { + if t >= e { + return 0 + } + return -1 + }) + if !ok { + return base | 1 + } + return primes[i] +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/ollama_model.go b/vendor/github.com/gpustack/gguf-parser-go/ollama_model.go new file mode 100644 index 00000000..3ffd950d --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/ollama_model.go @@ -0,0 +1,432 @@ +package gguf_parser + +import ( + "context" + "fmt" + "net/http" + "net/url" + "regexp" + "strings" + + "golang.org/x/sync/errgroup" + + "github.com/gpustack/gguf-parser-go/util/httpx" + "github.com/gpustack/gguf-parser-go/util/json" + "github.com/gpustack/gguf-parser-go/util/stringx" +) + +// Inspired by https://github.com/ollama/ollama/blob/380e06e5bea06ae8ded37f47c37bd5d604194d3e/types/model/name.go, +// and https://github.com/ollama/ollama/blob/380e06e5bea06ae8ded37f47c37bd5d604194d3e/server/modelpath.go. + +const ( + OllamaDefaultScheme = "https" + OllamaDefaultRegistry = "registry.ollama.ai" + OllamaDefaultNamespace = "library" + OllamaDefaultTag = "latest" +) + +type ( + // OllamaModel represents an Ollama model, + // its manifest(including MediaType, Config and Layers) can be completed further by calling the Complete method. + OllamaModel struct { + Schema string `json:"schema"` + Registry string `json:"registry"` + Namespace string `json:"namespace"` + Repository string `json:"repository"` + Tag string `json:"tag"` + SchemaVersion uint32 `json:"schemaVersion"` + MediaType string `json:"mediaType"` + Config OllamaModelLayer `json:"config"` + Layers []OllamaModelLayer `json:"layers"` + + // Client is the http client used to complete the OllamaModel's network operations. + // + // When this field is nil, + // it will be set to the client used by OllamaModel.Complete. + // + // When this field is offered, + // the network operations will be done with this client. + Client *http.Client `json:"-"` + } + + // OllamaModelLayer represents an Ollama model layer, + // its digest can be used to download the artifact. + OllamaModelLayer struct { + MediaType string `json:"mediaType"` + Size uint64 `json:"size"` + Digest string `json:"digest"` + + // Root points to the root OllamaModel, + // which is never serialized or deserialized. + // + // When called OllamaModel.Complete, + // this field will be set to the OllamaModel itself. + // If not, this field will be nil, + // and must be set manually to the root OllamaModel before calling the method of OllamaModelLayer. + Root *OllamaModel `json:"-"` + } +) + +// ParseOllamaModel parses the given Ollama model string, +// and returns the OllamaModel, or nil if the model is invalid. +func ParseOllamaModel(model string, opts ...OllamaModelOption) *OllamaModel { + if model == "" { + return nil + } + + var o _OllamaModelOptions + for _, opt := range opts { + opt(&o) + } + + om := OllamaModel{ + Schema: OllamaDefaultScheme, + Registry: OllamaDefaultRegistry, + Namespace: OllamaDefaultNamespace, + Tag: OllamaDefaultTag, + } + { + if o.DefaultScheme != "" { + om.Schema = o.DefaultScheme + } + if o.DefaultRegistry != "" { + om.Registry = o.DefaultRegistry + } + if o.DefaultNamespace != "" { + om.Namespace = o.DefaultNamespace + } + if o.DefaultTag != "" { + om.Tag = o.DefaultTag + } + } + + m := model + + // Drop digest. + m, _, _ = stringx.CutFromRight(m, "@") + + // Get tag. + m, s, ok := stringx.CutFromRight(m, ":") + if ok && s != "" { + om.Tag = s + } + + // Get repository. + m, s, ok = stringx.CutFromRight(m, "/") + if ok && s != "" { + om.Repository = s + } else if m != "" { + om.Repository = m + m = "" + } + + // Get namespace. + m, s, ok = stringx.CutFromRight(m, "/") + if ok && s != "" { + om.Namespace = s + } else if m != "" { + om.Namespace = m + m = "" + } + + // Get registry. + m, s, ok = stringx.CutFromLeft(m, "://") + if ok && s != "" { + om.Schema = m + om.Registry = s + } else if m != "" { + om.Registry = m + } + + if om.Repository == "" { + return nil + } + return &om +} + +func (om *OllamaModel) String() string { + var b strings.Builder + if om.Registry != "" { + b.WriteString(om.Registry) + b.WriteByte('/') + } + if om.Namespace != "" { + b.WriteString(om.Namespace) + b.WriteByte('/') + } + b.WriteString(om.Repository) + if om.Tag != "" { + b.WriteByte(':') + b.WriteString(om.Tag) + } + return b.String() +} + +// GetLayer returns the OllamaModelLayer with the given media type, +// and true if found, and false otherwise. +func (om *OllamaModel) GetLayer(mediaType string) (OllamaModelLayer, bool) { + for i := range om.Layers { + if om.Layers[i].MediaType == mediaType { + return om.Layers[i], true + } + } + return OllamaModelLayer{}, false +} + +// SearchLayers returns a list of OllamaModelLayer with the media type that matches the given regex. +func (om *OllamaModel) SearchLayers(mediaTypeRegex *regexp.Regexp) []OllamaModelLayer { + var ls []OllamaModelLayer + for i := range om.Layers { + if mediaTypeRegex.MatchString(om.Layers[i].MediaType) { + ls = append(ls, om.Layers[i]) + } + } + return ls +} + +// WebPageURL returns the Ollama web page URL of the OllamaModel. +func (om *OllamaModel) WebPageURL() *url.URL { + u := &url.URL{ + Scheme: om.Schema, + Host: om.Registry, + } + return u.JoinPath(om.Namespace, om.Repository+":"+om.Tag) +} + +// Complete completes the OllamaModel with the given context and http client. +func (om *OllamaModel) Complete(ctx context.Context, cli *http.Client) error { + if om.Client == nil { + om.Client = cli + } + + u := &url.URL{ + Scheme: om.Schema, + Host: om.Registry, + } + u = u.JoinPath("v2", om.Namespace, om.Repository, "manifests", om.Tag) + + req, err := httpx.NewGetRequestWithContext(ctx, u.String()) + if err != nil { + return fmt.Errorf("new request: %w", err) + } + req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") + + err = httpx.Do(om.Client, req, func(resp *http.Response) error { + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("status code %d", resp.StatusCode) + } + return json.NewDecoder(resp.Body).Decode(om) + }) + if err != nil { + return fmt.Errorf("do request %s: %w", u, err) + } + + // Connect. + om.Config.Root = om + for i := range om.Layers { + om.Layers[i].Root = om + } + + return nil +} + +// Params returns the parameters of the OllamaModel. +func (om *OllamaModel) Params(ctx context.Context, cli *http.Client) (map[string]any, error) { + if cli == nil { + cli = om.Client + } + if cli == nil { + return nil, fmt.Errorf("no client") + } + + mls := om.SearchLayers(regexp.MustCompile(`^application/vnd\.ollama\.image\.params$`)) + if len(mls) == 0 { + return nil, nil + } + + rs := make([]map[string]any, len(mls)) + eg, ctx := errgroup.WithContext(ctx) + for i := range mls { + x := i + eg.Go(func() error { + bs, err := mls[x].FetchBlob(ctx, cli) + if err == nil { + p := make(map[string]any) + if err = json.Unmarshal(bs, &p); err == nil { + rs[x] = p + } + } + return err + }) + } + if err := eg.Wait(); err != nil { + return nil, fmt.Errorf("fetch blob: %w", err) + } + + r := make(map[string]any) + for i := range rs { + for k, v := range rs[i] { + r[k] = v + } + } + return r, nil +} + +// Template returns the template of the OllamaModel. +func (om *OllamaModel) Template(ctx context.Context, cli *http.Client) (string, error) { + if cli == nil { + cli = om.Client + } + if cli == nil { + return "", fmt.Errorf("no client") + } + + mls := om.SearchLayers(regexp.MustCompile(`^application/vnd\.ollama\.image\.(prompt|template)$`)) + if len(mls) == 0 { + return "", nil + } + + ml := mls[len(mls)-1] + bs, err := ml.FetchBlob(ctx, cli) + if err != nil { + return "", fmt.Errorf("fetch blob: %w", err) + } + return stringx.FromBytes(&bs), nil +} + +// System returns the system message of the OllamaModel. +func (om *OllamaModel) System(ctx context.Context, cli *http.Client) (string, error) { + if cli == nil { + cli = om.Client + } + if cli == nil { + return "", fmt.Errorf("no client") + } + + mls := om.SearchLayers(regexp.MustCompile(`^application/vnd\.ollama\.image\.system$`)) + if len(mls) == 0 { + return "", nil + } + + ml := mls[len(mls)-1] + bs, err := ml.FetchBlob(ctx, cli) + if err != nil { + return "", fmt.Errorf("fetch blob: %w", err) + } + return stringx.FromBytes(&bs), nil +} + +// License returns the license of the OllamaModel. +func (om *OllamaModel) License(ctx context.Context, cli *http.Client) ([]string, error) { + if cli == nil { + cli = om.Client + } + if cli == nil { + return nil, fmt.Errorf("no client") + } + + mls := om.SearchLayers(regexp.MustCompile(`^application/vnd\.ollama\.image\.license$`)) + if len(mls) == 0 { + return nil, nil + } + + rs := make([]string, len(mls)) + eg, ctx := errgroup.WithContext(ctx) + for i := range mls { + x := i + eg.Go(func() error { + bs, err := mls[x].FetchBlob(ctx, cli) + if err == nil { + rs[x] = stringx.FromBytes(&bs) + } + return err + }) + } + if err := eg.Wait(); err != nil { + return nil, fmt.Errorf("fetch blob: %w", err) + } + return rs, nil +} + +// Messages returns the messages of the OllamaModel. +func (om *OllamaModel) Messages(ctx context.Context, cli *http.Client) ([]json.RawMessage, error) { + if cli == nil { + cli = om.Client + } + if cli == nil { + return nil, fmt.Errorf("no client") + } + + mls := om.SearchLayers(regexp.MustCompile(`^application/vnd\.ollama\.image\.messages$`)) + if len(mls) == 0 { + return nil, nil + } + + rs := make([]json.RawMessage, len(mls)) + eg, ctx := errgroup.WithContext(ctx) + for i := range mls { + x := i + eg.Go(func() error { + bs, err := mls[x].FetchBlob(ctx, cli) + if err == nil { + rs[x] = bs + } + return err + }) + } + if err := eg.Wait(); err != nil { + return nil, fmt.Errorf("fetch blob: %w", err) + } + return rs, nil +} + +// BlobURL returns the blob URL of the OllamaModelLayer. +func (ol *OllamaModelLayer) BlobURL() *url.URL { + if ol.Root == nil { + return nil + } + + u := &url.URL{ + Scheme: ol.Root.Schema, + Host: ol.Root.Registry, + } + return u.JoinPath("v2", ol.Root.Namespace, ol.Root.Repository, "blobs", ol.Digest) +} + +// FetchBlob fetches the blob of the OllamaModelLayer with the given context and http client, +// and returns the response body as bytes. +func (ol *OllamaModelLayer) FetchBlob(ctx context.Context, cli *http.Client) ([]byte, error) { + var b []byte + err := ol.FetchBlobFunc(ctx, cli, func(resp *http.Response) error { + b = httpx.BodyBytes(resp) + return nil + }) + return b, err +} + +// FetchBlobFunc fetches the blob of the OllamaModelLayer with the given context and http client, +// and processes the response with the given function. +func (ol *OllamaModelLayer) FetchBlobFunc(ctx context.Context, cli *http.Client, process func(*http.Response) error) error { + if cli == nil { + cli = ol.Root.Client + } + if cli == nil { + return fmt.Errorf("no client") + } + + u := ol.BlobURL() + if u == nil { + return fmt.Errorf("no blob URL") + } + + req, err := httpx.NewGetRequestWithContext(ctx, u.String()) + if err != nil { + return fmt.Errorf("new request: %w", err) + } + + err = httpx.Do(cli, req, process) + if err != nil { + return fmt.Errorf("do request %s: %w", u, err) + } + return nil +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/ollama_model_option.go b/vendor/github.com/gpustack/gguf-parser-go/ollama_model_option.go new file mode 100644 index 00000000..325d4eb7 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/ollama_model_option.go @@ -0,0 +1,79 @@ +package gguf_parser + +import ( + "net/url" + "strings" +) + +type ( + _OllamaModelOptions struct { + DefaultScheme string + DefaultRegistry string + DefaultNamespace string + DefaultTag string + } + OllamaModelOption func(*_OllamaModelOptions) +) + +// SetOllamaModelBaseURL parses the given base URL, +// and sets default schema/registry for OllamaModel. +func SetOllamaModelBaseURL(baseURL string) OllamaModelOption { + baseURL = strings.TrimSpace(baseURL) + return func(o *_OllamaModelOptions) { + if baseURL == "" { + return + } + + if !strings.Contains(baseURL, "://") { + baseURL = "https://" + baseURL + } + + u, err := url.Parse(baseURL) + if err != nil { + return + } + + o.DefaultScheme = u.Scheme + o.DefaultRegistry = u.Host + } +} + +// SetOllamaModelDefaultScheme sets the default scheme for OllamaModel. +func SetOllamaModelDefaultScheme(scheme string) OllamaModelOption { + return func(o *_OllamaModelOptions) { + if scheme == "" { + return + } + o.DefaultScheme = scheme + } +} + +// SetOllamaModelDefaultRegistry sets the default registry for OllamaModel. +func SetOllamaModelDefaultRegistry(registry string) OllamaModelOption { + return func(o *_OllamaModelOptions) { + if registry == "" { + return + } + o.DefaultRegistry = registry + } +} + +// SetOllamaModelDefaultNamespace sets the default namespace for OllamaModel. +func SetOllamaModelDefaultNamespace(namespace string) OllamaModelOption { + return func(o *_OllamaModelOptions) { + if namespace == "" { + return + } + o.DefaultNamespace = namespace + } +} + +// SetOllamaModelDefaultTag sets the default tag for OllamaModel. +func SetOllamaModelDefaultTag(tag string) OllamaModelOption { + return func(o *_OllamaModelOptions) { + if tag == "" { + return + } + o.DefaultTag = tag + } +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/ollama_registry_authenticate.go b/vendor/github.com/gpustack/gguf-parser-go/ollama_registry_authenticate.go new file mode 100644 index 00000000..45c4cb81 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/ollama_registry_authenticate.go @@ -0,0 +1,214 @@ +package gguf_parser + +import ( + "bytes" + "context" + "crypto/ed25519" + "crypto/rand" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "golang.org/x/crypto/ssh" + + "github.com/gpustack/gguf-parser-go/util/funcx" + "github.com/gpustack/gguf-parser-go/util/httpx" + "github.com/gpustack/gguf-parser-go/util/osx" + "github.com/gpustack/gguf-parser-go/util/stringx" +) + +const ( + httpHeaderWWWAuthenticate = "WWW-Authenticate" + httpHeaderAuthorization = "Authorization" +) + +// OllamaUserAgent returns the user agent string for Ollama, +// since llama3.1, the user agent is required to be set, +// otherwise the request will be rejected by 412. +func OllamaUserAgent() string { + return fmt.Sprintf("ollama/0.3.3 (%s %s) Go/%s", runtime.GOARCH, runtime.GOOS, runtime.Version()) +} + +// OllamaRegistryAuthorizeRetry returns true if the request should be retried with authorization. +// +// OllamaRegistryAuthorizeRetry leverages OllamaRegistryAuthorize to obtain an authorization token, +// and configures the request with the token. +func OllamaRegistryAuthorizeRetry(resp *http.Response, cli *http.Client) bool { + if resp == nil || cli == nil { + return false + } + + if resp.StatusCode != http.StatusUnauthorized && resp.Request == nil { + // Not unauthorized, return. + return false + } + + req := resp.Request + if req.Header.Get(httpHeaderAuthorization) != "" { + // Already authorized, return. + return false + } + + const tokenPrefix = "Bearer " + authnToken := strings.TrimPrefix(resp.Header.Get(httpHeaderWWWAuthenticate), tokenPrefix) + if authnToken == "" { + // No authentication token, return. + return false + } + authzToken := funcx.MustNoError(OllamaRegistryAuthorize(req.Context(), cli, authnToken)) + req.Header.Set(httpHeaderAuthorization, tokenPrefix+authzToken) + return true +} + +// OllamaRegistryAuthorize authorizes the request with the given authentication token, +// and returns the authorization token. +func OllamaRegistryAuthorize(ctx context.Context, cli *http.Client, authnToken string) (string, error) { + priKey, err := OllamaSingKeyLoad() + if err != nil { + return "", fmt.Errorf("load sign key: %w", err) + } + + var authzUrl string + { + ss := strings.Split(authnToken, ",") + if len(ss) < 3 { + return "", errors.New("invalid authn token") + } + + var realm, service, scope string + for _, s := range ss { + sp := strings.SplitN(s, "=", 2) + if len(sp) < 2 { + continue + } + sp[1] = strings.TrimFunc(sp[1], func(r rune) bool { + return r == '"' || r == '\'' + }) + switch sp[0] { + case "realm": + realm = sp[1] + case "service": + service = sp[1] + case "scope": + scope = sp[1] + } + } + + u, err := url.Parse(realm) + if err != nil { + return "", fmt.Errorf("parse realm: %w", err) + } + + qs := u.Query() + qs.Add("service", service) + for _, s := range strings.Split(scope, " ") { + qs.Add("scope", s) + } + qs.Add("ts", strconv.FormatInt(time.Now().Unix(), 10)) + qs.Add("nonce", stringx.RandomBase64(16)) + u.RawQuery = qs.Encode() + + authzUrl = u.String() + } + + var authnData string + { + pubKey := ssh.MarshalAuthorizedKey(priKey.PublicKey()) + pubKeyp := bytes.Split(pubKey, []byte(" ")) + if len(pubKeyp) < 2 { + return "", errors.New("malformed public key") + } + + nc := base64.StdEncoding.EncodeToString([]byte(stringx.SumBytesBySHA256(nil))) + py := []byte(fmt.Sprintf("%s,%s,%s", http.MethodGet, authzUrl, nc)) + sd, err := priKey.Sign(rand.Reader, py) + if err != nil { + return "", fmt.Errorf("signing data: %w", err) + } + authnData = fmt.Sprintf("%s:%s", bytes.TrimSpace(pubKeyp[1]), base64.StdEncoding.EncodeToString(sd.Blob)) + } + + req, err := httpx.NewGetRequestWithContext(ctx, authzUrl) + if err != nil { + return "", fmt.Errorf("new request: %w", err) + } + req.Header.Add(httpHeaderAuthorization, authnData) + + var authzToken string + err = httpx.Do(cli, req, func(resp *http.Response) error { + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("status code %d", resp.StatusCode) + } + var tok struct { + Token string `json:"token"` + } + if err = json.NewDecoder(resp.Body).Decode(&tok); err != nil { + return err + } + if tok.Token == "" { + return errors.New("empty token") + } + authzToken = tok.Token + return nil + }) + if err != nil { + return "", fmt.Errorf("do request %s: %w", authzUrl, err) + } + + return authzToken, nil +} + +// OllamaSingKeyLoad loads the signing key for Ollama, +// and generates a new key if not exists. +func OllamaSingKeyLoad() (ssh.Signer, error) { + hd := filepath.Join(osx.UserHomeDir(), ".ollama") + + priKeyPath := filepath.Join(hd, "id_ed25519") + if !osx.ExistsFile(priKeyPath) { + // Generate key if not exists. + pubKey, priKey, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, fmt.Errorf("generate key: %w", err) + } + + priKeyPem, err := ssh.MarshalPrivateKey(priKey, "") + if err != nil { + return nil, fmt.Errorf("marshal private key: %w", err) + } + priKeyBs := pem.EncodeToMemory(priKeyPem) + + sshPubKey, err := ssh.NewPublicKey(pubKey) + if err != nil { + return nil, fmt.Errorf("new public key: %w", err) + } + pubKeyBs := ssh.MarshalAuthorizedKey(sshPubKey) + + if err = osx.WriteFile(priKeyPath, priKeyBs, 0o600); err != nil { + return nil, fmt.Errorf("write private key: %w", err) + } + if err = osx.WriteFile(priKeyPath+".pub", pubKeyBs, 0o644); err != nil { + _ = os.Remove(priKeyPath) + return nil, fmt.Errorf("write public key: %w", err) + } + } + + priKeyBs, err := os.ReadFile(priKeyPath) + if err != nil { + return nil, fmt.Errorf("read private key: %w", err) + } + priKey, err := ssh.ParsePrivateKey(priKeyBs) + if err != nil { + return nil, fmt.Errorf("parse private key: %w", err) + } + return priKey, nil +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/scalar.go b/vendor/github.com/gpustack/gguf-parser-go/scalar.go new file mode 100644 index 00000000..35038ddd --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/scalar.go @@ -0,0 +1,291 @@ +package gguf_parser + +import ( + "errors" + "strconv" + "strings" +) + +const ( + _Ki = 1 << ((iota + 1) * 10) + _Mi + _Gi + _Ti + _Pi +) + +const ( + _K = 1e3 + _M = 1e6 + _G = 1e9 + _T = 1e12 + _P = 1e15 +) + +const ( + _Thousand = 1e3 + _Million = 1e6 + _Billion = 1e9 + _Trillion = 1e12 + _Quadrillion = 1e15 +) + +type ( + // SizeScalar is the scalar for size. + SizeScalar uint64 + + // FLOPSScalar is the scalar for FLOPS. + FLOPSScalar uint64 + + // BytesPerSecondScalar is the scalar for bytes per second (Bps). + BytesPerSecondScalar uint64 +) + +var ( + // _GeneralBaseUnitMatrix is the base unit matrix for bytes. + _GeneralBaseUnitMatrix = []struct { + Base float64 + Unit string + }{ + {_Pi, "Pi"}, + {_P, "P"}, + {_Ti, "Ti"}, + {_T, "T"}, + {_Gi, "Gi"}, + {_G, "G"}, + {_Mi, "Mi"}, + {_M, "M"}, + {_Ki, "Ki"}, + {_K, "K"}, + } + + // _SizeBaseUnitMatrix is the base unit matrix for size. + _SizeBaseUnitMatrix = []struct { + Base float64 + Unit string + }{ + {_Pi, "P"}, + {_Ti, "T"}, + {_Gi, "G"}, + {_Mi, "M"}, + {_Ki, "K"}, + } + + // _NumberBaseUnitMatrix is the base unit matrix for numbers. + _NumberBaseUnitMatrix = []struct { + Base float64 + Unit string + }{ + {_Quadrillion, "Q"}, + {_Trillion, "T"}, + {_Billion, "B"}, + {_Million, "M"}, + {_Thousand, "K"}, + } +) + +// ParseSizeScalar parses the SizeScalar from the string. +func ParseSizeScalar(s string) (_ SizeScalar, err error) { + if s == "" { + return 0, errors.New("invalid SizeScalar") + } + b := float64(1) + for i := range _SizeBaseUnitMatrix { + if strings.HasSuffix(s, _SizeBaseUnitMatrix[i].Unit) { + b = _SizeBaseUnitMatrix[i].Base + s = strings.TrimSuffix(s, _SizeBaseUnitMatrix[i].Unit) + break + } + } + f, err := strconv.ParseFloat(strings.TrimSpace(s), 64) + if err != nil { + return 0, err + } + return SizeScalar(f * b), nil +} + +func (s SizeScalar) String() string { + if s == 0 { + return "0" + } + b, u := float64(1), "" + for i := range _SizeBaseUnitMatrix { + if float64(s) >= _SizeBaseUnitMatrix[i].Base { + b = _SizeBaseUnitMatrix[i].Base + u = _SizeBaseUnitMatrix[i].Unit + break + } + } + f := strconv.FormatFloat(float64(s)/b, 'f', 2, 64) + return strings.TrimSuffix(f, ".00") + " " + u +} + +// ParseFLOPSScalar parses the FLOPSScalar from the string. +func ParseFLOPSScalar(s string) (_ FLOPSScalar, err error) { + if s == "" { + return 0, errors.New("invalid FLOPSScalar") + } + s = strings.TrimSuffix(s, "FLOPS") + b := float64(1) + for i := range _GeneralBaseUnitMatrix { + if strings.HasSuffix(s, _GeneralBaseUnitMatrix[i].Unit) { + b = _GeneralBaseUnitMatrix[i].Base + s = strings.TrimSuffix(s, _GeneralBaseUnitMatrix[i].Unit) + break + } + } + f, err := strconv.ParseFloat(strings.TrimSpace(s), 64) + if err != nil { + return 0, err + } + return FLOPSScalar(f * b), nil +} + +func (s FLOPSScalar) String() string { + if s == 0 { + return "0 FLOPS" + } + b, u := float64(1), "" + for i := range _GeneralBaseUnitMatrix { + if float64(s) >= _GeneralBaseUnitMatrix[i].Base { + b = _GeneralBaseUnitMatrix[i].Base + u = _GeneralBaseUnitMatrix[i].Unit + break + } + } + f := strconv.FormatFloat(float64(s)/b, 'f', 2, 64) + return strings.TrimSuffix(f, ".00") + " " + u + "FLOPS" +} + +// ParseBytesPerSecondScalar parses the BytesPerSecondScalar from the string. +func ParseBytesPerSecondScalar(s string) (_ BytesPerSecondScalar, err error) { + if s == "" { + return 0, errors.New("invalid BytesPerSecondScalar") + } + b := float64(1) + o := float64(1) + switch { + case strings.HasSuffix(s, "Bps") || strings.HasSuffix(s, "B/s"): + s = strings.TrimSuffix(strings.TrimSuffix(s, "Bps"), "B/s") + case strings.HasSuffix(s, "bps") || strings.HasSuffix(s, "b/s"): + s = strings.TrimSuffix(strings.TrimSuffix(s, "bps"), "b/s") + o = 8 + } + for i := range _GeneralBaseUnitMatrix { + if strings.HasSuffix(s, _GeneralBaseUnitMatrix[i].Unit) { + b = _GeneralBaseUnitMatrix[i].Base + s = strings.TrimSuffix(s, _GeneralBaseUnitMatrix[i].Unit) + break + } + } + f, err := strconv.ParseFloat(strings.TrimSpace(s), 64) + if err != nil { + return 0, err + } + return BytesPerSecondScalar(f * b / o), nil +} + +func (s BytesPerSecondScalar) String() string { + if s == 0 { + return "0 Bps" + } + b, u := float64(1), "" + for i := range _GeneralBaseUnitMatrix { + if float64(s) >= _GeneralBaseUnitMatrix[i].Base { + b = _GeneralBaseUnitMatrix[i].Base + u = _GeneralBaseUnitMatrix[i].Unit + break + } + } + f := strconv.FormatFloat(float64(s)/b, 'f', 2, 64) + return strings.TrimSuffix(f, ".00") + " " + u + "Bps" +} + +type ( + // GGUFBytesScalar is the scalar for bytes. + GGUFBytesScalar uint64 + + // GGUFParametersScalar is the scalar for parameters. + GGUFParametersScalar uint64 + + // GGUFBitsPerWeightScalar is the scalar for bits per weight. + GGUFBitsPerWeightScalar float64 + + // GGUFTokensPerSecondScalar is the scalar for tokens per second. + GGUFTokensPerSecondScalar float64 +) + +// ParseGGUFBytesScalar parses the GGUFBytesScalar from the string. +func ParseGGUFBytesScalar(s string) (_ GGUFBytesScalar, err error) { + if s == "" { + return 0, errors.New("invalid GGUFBytesScalar") + } + s = strings.TrimSuffix(s, "B") + b := float64(1) + for i := range _GeneralBaseUnitMatrix { + if strings.HasSuffix(s, _GeneralBaseUnitMatrix[i].Unit) { + b = _GeneralBaseUnitMatrix[i].Base + s = strings.TrimSuffix(s, _GeneralBaseUnitMatrix[i].Unit) + break + } + } + f, err := strconv.ParseFloat(strings.TrimSpace(s), 64) + if err != nil { + return 0, err + } + return GGUFBytesScalar(f * b), nil +} + +// GGUFBytesScalarStringInMiBytes is the flag to show the GGUFBytesScalar string in MiB. +var GGUFBytesScalarStringInMiBytes bool + +func (s GGUFBytesScalar) String() string { + if s == 0 { + return "0 B" + } + b, u := float64(1), "" + if GGUFBytesScalarStringInMiBytes { + b = _Mi + u = "Mi" + } else { + for i := range _GeneralBaseUnitMatrix { + if float64(s) >= _GeneralBaseUnitMatrix[i].Base { + b = _GeneralBaseUnitMatrix[i].Base + u = _GeneralBaseUnitMatrix[i].Unit + break + } + } + } + f := strconv.FormatFloat(float64(s)/b, 'f', 2, 64) + return strings.TrimSuffix(f, ".00") + " " + u + "B" +} + +func (s GGUFParametersScalar) String() string { + if s == 0 { + return "0" + } + b, u := float64(1), "" + for i := range _NumberBaseUnitMatrix { + if float64(s) >= _NumberBaseUnitMatrix[i].Base { + b = _NumberBaseUnitMatrix[i].Base + u = _NumberBaseUnitMatrix[i].Unit + break + } + } + f := strconv.FormatFloat(float64(s)/b, 'f', 2, 64) + return strings.TrimSuffix(f, ".00") + " " + u +} + +func (s GGUFBitsPerWeightScalar) String() string { + if s <= 0 { + return "0 bpw" + } + return strconv.FormatFloat(float64(s), 'f', 2, 64) + " bpw" +} + +func (s GGUFTokensPerSecondScalar) String() string { + if s <= 0 { + return "0 tps" + } + return strconv.FormatFloat(float64(s), 'f', 2, 64) + " tps" +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/anyx/any.go b/vendor/github.com/gpustack/gguf-parser-go/util/anyx/any.go new file mode 100644 index 00000000..8c74fa18 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/anyx/any.go @@ -0,0 +1,128 @@ +package anyx + +import ( + "encoding/json" + "fmt" + "strconv" + + "golang.org/x/exp/constraints" +) + +// Number converts any type to the specified number type. +func Number[T constraints.Integer | constraints.Float](v any) T { + switch vv := v.(type) { + case int: + return T(vv) + case int8: + return T(vv) + case int16: + return T(vv) + case int32: + return T(vv) + case int64: + return T(vv) + case uint: + return T(vv) + case uint8: + return T(vv) + case uint16: + return T(vv) + case uint32: + return T(vv) + case uint64: + return T(vv) + case float32: + return T(vv) + case float64: + return T(vv) + case bool: + if vv { + return T(1) + } + return T(0) + case string: + x, err := strconv.ParseInt(vv, 10, 64) + if err != nil { + y, err := strconv.ParseFloat(vv, 64) + if err != nil { + return T(0) + } else { + return T(y) + } + } + return T(x) + case json.Number: + x, err := vv.Int64() + if err != nil { + y, err := vv.Float64() + if err != nil { + return T(0) + } else { + return T(y) + } + } + return T(x) + default: + return T(0) + } +} + +// Bool converts any type to a bool. +func Bool(v any) bool { + switch vv := v.(type) { + case bool: + return vv + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr: + return vv != 0 + case float32, float64: + return vv != 0 + case string: + return vv != "0" + case fmt.Stringer: + return vv.String() != "0" + default: + return false + } +} + +// String converts any type to a string. +func String(v any) string { + switch vv := v.(type) { + case string: + return vv + case []byte: + return string(vv) + case int: + return strconv.FormatInt(int64(vv), 10) + case int8: + return strconv.FormatInt(int64(vv), 10) + case int16: + return strconv.FormatInt(int64(vv), 10) + case int32: + return strconv.FormatInt(int64(vv), 10) + case int64: + return strconv.FormatInt(vv, 10) + case uint: + return strconv.FormatUint(uint64(vv), 10) + case uint8: + return strconv.FormatUint(uint64(vv), 10) + case uint16: + return strconv.FormatUint(uint64(vv), 10) + case uint32: + return strconv.FormatUint(uint64(vv), 10) + case uint64: + return strconv.FormatUint(vv, 10) + case float32: + return strconv.FormatFloat(float64(vv), 'f', -1, 32) + case float64: + return strconv.FormatFloat(vv, 'f', -1, 64) + case bool: + return strconv.FormatBool(vv) + case fmt.Stringer: + return vv.String() + case json.RawMessage: + return string(vv) + default: + return fmt.Sprintf("%v", v) + } +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/bytex/pool.go b/vendor/github.com/gpustack/gguf-parser-go/util/bytex/pool.go new file mode 100644 index 00000000..6178c048 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/bytex/pool.go @@ -0,0 +1,91 @@ +package bytex + +import ( + "bytes" + "sync" +) + +const defaultSize = 32 * 1024 + +type ( + Bytes = []byte + BytesBuffer = *bytes.Buffer +) + +var gp = sync.Pool{ + New: func() any { + buf := make(Bytes, defaultSize) + return &buf + }, +} + +// GetBytes gets a bytes buffer from the pool, +// which can specify with a size, +// default is 32k. +func GetBytes(size ...uint64) Bytes { + buf := *(gp.Get().(*Bytes)) + + s := defaultSize + if len(size) != 0 { + s = int(size[0]) + if s == 0 { + s = defaultSize + } + } + if cap(buf) >= s { + return buf[:s] + } + + gp.Put(&buf) + + ns := s + if ns < defaultSize { + ns = defaultSize + } + buf = make(Bytes, ns) + return buf[:s] +} + +// WithBytes relies on GetBytes to get a buffer, +// calls the function with the buffer, +// finally, puts it back to the pool after the function returns. +func WithBytes(fn func(Bytes) error, size ...uint64) error { + if fn == nil { + return nil + } + + buf := GetBytes(size...) + defer Put(buf) + return fn(buf) +} + +// GetBuffer is similar to GetBytes, +// but it returns the bytes buffer wrapped by bytes.Buffer. +func GetBuffer(size ...uint64) BytesBuffer { + return bytes.NewBuffer(GetBytes(size...)[:0]) +} + +// WithBuffer relies on GetBuffer to get a buffer, +// calls the function with the buffer, +// finally, puts it back to the pool after the function returns. +func WithBuffer(fn func(BytesBuffer) error, size ...uint64) error { + if fn == nil { + return nil + } + + buf := GetBuffer(size...) + defer Put(buf) + return fn(buf) +} + +// Put puts the buffer(either Bytes or BytesBuffer) back to the pool. +func Put[T Bytes | BytesBuffer](buf T) { + switch v := any(buf).(type) { + case Bytes: + gp.Put(&v) + case BytesBuffer: + bs := v.Bytes() + gp.Put(&bs) + v.Reset() + } +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/funcx/error.go b/vendor/github.com/gpustack/gguf-parser-go/util/funcx/error.go new file mode 100644 index 00000000..10235536 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/funcx/error.go @@ -0,0 +1,65 @@ +package funcx + +// NoError ignores the given error, +// it is usually a nice helper for chain function calling. +func NoError[T any](t T, _ error) T { + return t +} + +// NoError2 ignores the given error, +// it is usually a nice helper for chain function calling. +func NoError2[T, U any](t T, u U, _ error) (T, U) { + return t, u +} + +// NoError3 ignores the given error, +// it is usually a nice helper for chain function calling. +func NoError3[T, U, V any](t T, u U, v V, _ error) (T, U, V) { + return t, u, v +} + +// NoError4 ignores the given error, +// it is usually a nice helper for chain function calling. +func NoError4[T, U, V, W any](t T, u U, v V, w W, _ error) (T, U, V, W) { + return t, u, v, w +} + +// MustNoError is similar to NoError, +// but it panics if the given error is not nil, +// it is usually a nice helper for chain function calling. +func MustNoError[T any](t T, e error) T { + if e != nil { + panic(e) + } + return t +} + +// MustNoError2 is similar to NoError2, +// but it panics if the given error is not nil, +// it is usually a nice helper for chain function calling. +func MustNoError2[T, U any](t T, u U, e error) (T, U) { + if e != nil { + panic(e) + } + return t, u +} + +// MustNoError3 is similar to NoError3, +// but it panics if the given error is not nil, +// it is usually a nice helper for chain function calling. +func MustNoError3[T, U, V any](t T, u U, v V, e error) (T, U, V) { + if e != nil { + panic(e) + } + return t, u, v +} + +// MustNoError4 is similar to NoError4, +// but it panics if the given error is not nil, +// it is usually a nice helper for chain function calling. +func MustNoError4[T, U, V, W any](t T, u U, v V, w W, e error) (T, U, V, W) { + if e != nil { + panic(e) + } + return t, u, v, w +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client.go new file mode 100644 index 00000000..bcab1619 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client.go @@ -0,0 +1,253 @@ +package httpx + +import ( + "context" + "fmt" + "io" + "net/http" + "time" + + "github.com/henvic/httpretty" + + "github.com/gpustack/gguf-parser-go/util/bytex" +) + +// DefaultClient is similar to the default http.Client used by the package. +// +// It is used for requests pooling. +var DefaultClient = &http.Client{ + Transport: DefaultTransport, +} + +// DefaultInsecureClient is the default http.Client used by the package, +// with TLS insecure skip verify. +// +// It is used for requests pooling. +var DefaultInsecureClient = &http.Client{ + Transport: DefaultInsecureTransport, +} + +// Client returns a new http.Client with the given options, +// the result http.Client is used for fast-consuming requests. +// +// If you want a requests pool management, use DefaultClient instead. +func Client(opts ...*ClientOption) *http.Client { + var o *ClientOption + if len(opts) > 0 { + o = opts[0] + } else { + o = ClientOptions() + } + + root := DefaultTransport + if o.transport != nil { + root = o.transport + } + + if o.debug { + pretty := &httpretty.Logger{ + Time: true, + TLS: true, + RequestHeader: true, + RequestBody: true, + MaxRequestBody: 1024, + ResponseHeader: true, + ResponseBody: true, + MaxResponseBody: 1024, + Formatters: []httpretty.Formatter{&JSONFormatter{}}, + } + root = pretty.RoundTripper(root) + } + + rtc := RoundTripperChain{ + Next: root, + } + for i := range o.roundTrippers { + rtc = RoundTripperChain{ + Do: o.roundTrippers[i], + Next: rtc, + } + } + + var rt http.RoundTripper = rtc + if o.retryIf != nil { + rt = RoundTripperFunc(func(req *http.Request) (*http.Response, error) { + for i := 0; ; i++ { + resp, err := rtc.RoundTrip(req) + if !o.retryIf(resp, err) { + return resp, err + } + w, ok := o.retryBackoff(i+1, resp) + if !ok { + return resp, err + } + wt := time.NewTimer(w) + select { + case <-req.Context().Done(): + wt.Stop() + return resp, req.Context().Err() + case <-wt.C: + } + } + }) + } + + return &http.Client{ + Transport: rt, + Timeout: o.timeout, + } +} + +// NewGetRequestWithContext returns a new http.MethodGet request, +// which is saving your life from http.NewRequestWithContext. +func NewGetRequestWithContext(ctx context.Context, uri string) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) +} + +// NewGetRequest returns a new http.MethodGet request, +// which is saving your life from http.NewRequest. +func NewGetRequest(uri string) (*http.Request, error) { + return http.NewRequest(http.MethodGet, uri, nil) +} + +// NewHeadRequestWithContext returns a new http.MethodHead request, +// which is saving your life from http.NewRequestWithContext. +func NewHeadRequestWithContext(ctx context.Context, uri string) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodHead, uri, nil) +} + +// NewHeadRequest returns a new http.MethodHead request, +// which is saving your life from http.NewRequest. +func NewHeadRequest(uri string) (*http.Request, error) { + return http.NewRequest(http.MethodHead, uri, nil) +} + +// NewPostRequestWithContext returns a new http.MethodPost request with the given context, +// which is saving your life from http.NewRequestWithContext. +func NewPostRequestWithContext(ctx context.Context, uri string, body io.Reader) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodPost, uri, body) +} + +// NewPostRequest returns a new http.MethodPost request, +// which is saving your life from http.NewRequest. +func NewPostRequest(uri string, body io.Reader) (*http.Request, error) { + return http.NewRequest(http.MethodPost, uri, body) +} + +// NewPutRequestWithContext returns a new http.MethodPut request with the given context, +// which is saving your life from http.NewRequestWithContext. +func NewPutRequestWithContext(ctx context.Context, uri string, body io.Reader) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodPut, uri, body) +} + +// NewPutRequest returns a new http.MethodPut request, +// which is saving your life from http.NewRequest. +func NewPutRequest(uri string, body io.Reader) (*http.Request, error) { + return http.NewRequest(http.MethodPut, uri, body) +} + +// NewPatchRequestWithContext returns a new http.MethodPatch request with the given context, +// which is saving your life from http.NewRequestWithContext. +func NewPatchRequestWithContext(ctx context.Context, uri string, body io.Reader) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodPatch, uri, body) +} + +// NewPatchRequest returns a new http.MethodPatch request, +// which is saving your life from http.NewRequest. +func NewPatchRequest(uri string, body io.Reader) (*http.Request, error) { + return http.NewRequest(http.MethodPatch, uri, body) +} + +// NewDeleteRequestWithContext returns a new http.MethodDelete request with the given context, +// which is saving your life from http.NewRequestWithContext. +func NewDeleteRequestWithContext(ctx context.Context, uri string) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodDelete, uri, nil) +} + +// NewDeleteRequest returns a new http.MethodDelete request, +// which is saving your life from http.NewRequest. +func NewDeleteRequest(uri string) (*http.Request, error) { + return http.NewRequest(http.MethodDelete, uri, nil) +} + +// NewConnectRequestWithContext returns a new http.MethodConnect request with the given context, +// which is saving your life from http.NewRequestWithContext. +func NewConnectRequestWithContext(ctx context.Context, uri string) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodConnect, uri, nil) +} + +// NewConnectRequest returns a new http.MethodConnect request, +// which is saving your life from http.NewRequest. +func NewConnectRequest(uri string) (*http.Request, error) { + return http.NewRequest(http.MethodConnect, uri, nil) +} + +// NewOptionsRequestWithContext returns a new http.MethodOptions request with the given context, +// which is saving your life from http.NewRequestWithContext. +func NewOptionsRequestWithContext(ctx context.Context, uri string) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodOptions, uri, nil) +} + +// NewOptionsRequest returns a new http.MethodOptions request, +// which is saving your life from http.NewRequest. +func NewOptionsRequest(uri string) (*http.Request, error) { + return http.NewRequest(http.MethodOptions, uri, nil) +} + +// NewTraceRequestWithContext returns a new http.MethodTrace request with the given context, +// which is saving your life from http.NewRequestWithContext. +func NewTraceRequestWithContext(ctx context.Context, uri string) (*http.Request, error) { + return http.NewRequestWithContext(ctx, http.MethodTrace, uri, nil) +} + +// NewTraceRequest returns a new http.MethodTrace request, +// which is saving your life from http.NewRequest. +func NewTraceRequest(uri string) (*http.Request, error) { + return http.NewRequest(http.MethodTrace, uri, nil) +} + +// Error is similar to http.Error, +// but it can get the error message by the given code. +func Error(rw http.ResponseWriter, code int) { + http.Error(rw, http.StatusText(code), code) +} + +// Close closes the http response body without error. +func Close(resp *http.Response) { + if resp != nil && resp.Body != nil { + _ = resp.Body.Close() + } +} + +// BodyBytes returns the body of the http response as a byte slice. +func BodyBytes(resp *http.Response) []byte { + buf := bytex.GetBytes() + defer bytex.Put(buf) + + w := bytex.GetBuffer() + _, _ = io.CopyBuffer(w, resp.Body, buf) + return w.Bytes() +} + +// BodyString returns the body of the http response as a string. +func BodyString(resp *http.Response) string { + return string(BodyBytes(resp)) +} + +// Do is a helper function to execute the given http request with the given http client, +// and execute the given function with the http response. +// +// It is useful to avoid forgetting to close the http response body. +// +// Do will return the error if failed to execute the http request or the given function. +func Do(cli *http.Client, req *http.Request, respFunc func(*http.Response) error) error { + resp, err := cli.Do(req) + if err != nil { + return fmt.Errorf("do request: %w", err) + } + defer Close(resp) + if respFunc == nil { + return nil + } + return respFunc(resp) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client_helper.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client_helper.go new file mode 100644 index 00000000..de0c231c --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client_helper.go @@ -0,0 +1,67 @@ +package httpx + +import ( + "bytes" + "errors" + "io" + "net/http" + "regexp" + + "github.com/henvic/httpretty" + + "github.com/gpustack/gguf-parser-go/util/json" +) + +var _ httpretty.Formatter = (*JSONFormatter)(nil) + +// JSONFormatter is copied from httpretty.JSONFormatter, +// but use our own json package. +type JSONFormatter struct{} + +var jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`) + +// Match JSON media type. +func (j *JSONFormatter) Match(mediatype string) bool { + return jsonTypeRE.MatchString(mediatype) +} + +// Format JSON content. +func (j *JSONFormatter) Format(w io.Writer, src []byte) error { + if !json.Valid(src) { + // We want to get the error of json.checkValid, not unmarshal it. + // The happy path has been optimized, maybe prematurely. + if err := json.Unmarshal(src, &json.RawMessage{}); err != nil { + return err + } + } + // Avoiding allocation as we use *bytes.Buffer to store the formatted body before printing + dst, ok := w.(*bytes.Buffer) + if !ok { + // Mitigating panic to avoid upsetting anyone who uses this directly + return errors.New("underlying writer for JSONFormatter must be *bytes.Buffer") + } + return json.Indent(dst, src, "", " ") +} + +type RoundTripperChain struct { + Do func(req *http.Request) error + Next http.RoundTripper +} + +func (c RoundTripperChain) RoundTrip(req *http.Request) (*http.Response, error) { + if c.Do != nil { + if err := c.Do(req); err != nil { + return nil, err + } + } + if c.Next != nil { + return c.Next.RoundTrip(req) + } + return nil, nil +} + +type RoundTripperFunc func(*http.Request) (*http.Response, error) + +func (fn RoundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return fn(req) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client_options.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client_options.go new file mode 100644 index 00000000..8a986f65 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/client_options.go @@ -0,0 +1,194 @@ +package httpx + +import ( + "math" + "net/http" + "strconv" + "strings" + "time" +) + +type ClientOption struct { + *TransportOption + + timeout time.Duration + debug bool + retryIf RetryFunc + retryBackoff func(attemptNum int, resp *http.Response) (wait time.Duration, ok bool) + roundTrippers []func(req *http.Request) error +} + +func ClientOptions() *ClientOption { + return &ClientOption{ + TransportOption: TransportOptions().WithoutKeepalive(), + timeout: 30 * time.Second, + retryIf: DefaultRetry, + retryBackoff: createRetryBackoff(100*time.Millisecond, 5*time.Second, 5), + } +} + +// WithTransport sets the TransportOption. +func (o *ClientOption) WithTransport(opt *TransportOption) *ClientOption { + if o == nil || opt == nil { + return o + } + o.TransportOption = opt + return o +} + +// WithTimeout sets the request timeout. +// +// This timeout controls the sum of [network dial], [tls handshake], [request], [response header reading] and [response body reading]. +// +// Use 0 to disable timeout. +func (o *ClientOption) WithTimeout(timeout time.Duration) *ClientOption { + if o == nil || timeout < 0 { + return o + } + o.timeout = timeout + return o +} + +// WithDebug sets the debug mode. +func (o *ClientOption) WithDebug() *ClientOption { + if o == nil { + return o + } + o.debug = true + return o +} + +type RetryFunc func(resp *http.Response, err error) (retry bool) + +// WithRetryIf specifies the if-condition of retry operation for request, +// or stops retrying if setting with `nil`. +func (o *ClientOption) WithRetryIf(retryIf RetryFunc) *ClientOption { + if o == nil { + return o + } + o.retryIf = retryIf + return o +} + +// WithRetryBackoff specifies the retry-backoff mechanism for request. +func (o *ClientOption) WithRetryBackoff(waitMin, waitMax time.Duration, attemptMax int) *ClientOption { + if o == nil || waitMin < 0 || waitMax < 0 || waitMax < waitMin || attemptMax <= 0 { + return o + } + o.retryBackoff = createRetryBackoff(waitMin, waitMax, attemptMax) + return o +} + +// WithUserAgent sets the user agent. +func (o *ClientOption) WithUserAgent(ua string) *ClientOption { + return o.WithRoundTripper(func(req *http.Request) error { + req.Header.Set("User-Agent", ua) + return nil + }) +} + +// WithBearerAuth sets the bearer token. +func (o *ClientOption) WithBearerAuth(token string) *ClientOption { + return o.WithRoundTripper(func(req *http.Request) error { + req.Header.Set("Authorization", "Bearer "+token) + return nil + }) +} + +// WithBasicAuth sets the basic authentication. +func (o *ClientOption) WithBasicAuth(username, password string) *ClientOption { + return o.WithRoundTripper(func(req *http.Request) error { + req.SetBasicAuth(username, password) + return nil + }) +} + +// WithHeader sets the header. +func (o *ClientOption) WithHeader(key, value string) *ClientOption { + return o.WithRoundTripper(func(req *http.Request) error { + req.Header.Set(key, value) + return nil + }) +} + +// WithHeaders sets the headers. +func (o *ClientOption) WithHeaders(headers map[string]string) *ClientOption { + return o.WithRoundTripper(func(req *http.Request) error { + for k, v := range headers { + req.Header.Set(k, v) + } + return nil + }) +} + +// WithRoundTripper sets the round tripper. +func (o *ClientOption) WithRoundTripper(rt func(req *http.Request) error) *ClientOption { + if o == nil || rt == nil { + return o + } + o.roundTrippers = append(o.roundTrippers, rt) + return o +} + +// If is a conditional option, +// which receives a boolean condition to trigger the given function or not. +func (o *ClientOption) If(condition bool, then func(*ClientOption) *ClientOption) *ClientOption { + if condition { + return then(o) + } + return o +} + +// DefaultRetry is the default retry condition, +// inspired by https://github.com/hashicorp/go-retryablehttp/blob/40b0cad1633fd521cee5884724fcf03d039aaf3f/client.go#L68-L86. +func DefaultRetry(resp *http.Response, respErr error) bool { + if respErr != nil { + switch errMsg := respErr.Error(); { + case strings.Contains(errMsg, `redirects`): + return false + case strings.Contains(errMsg, `unsupported protocol scheme`): + return false + case strings.Contains(errMsg, `certificate is not trusted`): + return false + case strings.Contains(errMsg, `invalid header`): + return false + case strings.Contains(errMsg, `failed to verify certificate`): + return false + } + + // Retry if receiving connection closed. + return true + } + + // Retry if receiving rate-limited of server. + if resp.StatusCode == http.StatusTooManyRequests { + return true + } + + // Retry if receiving unexpected responses. + if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != http.StatusNotImplemented) { + return true + } + + return false +} + +// createRetryBackoff creates a backoff function for retry operation. +func createRetryBackoff(waitMin, waitMax time.Duration, attemptMax int) func(int, *http.Response) (time.Duration, bool) { + return func(attemptNum int, resp *http.Response) (wait time.Duration, ok bool) { + if attemptNum > attemptMax { + return 0, false + } + + if resp != nil && (resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusServiceUnavailable) { + if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" { + if seconds, err := strconv.Atoi(retryAfter); err == nil { + return time.Duration(seconds) * time.Second, true + } + } + } + + wait = time.Duration(math.Pow(2, float64(attemptNum)) * float64(waitMin)) + return min(wait, waitMax), true + } +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/file.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/file.go new file mode 100644 index 00000000..04110308 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/file.go @@ -0,0 +1,226 @@ +package httpx + +import ( + "errors" + "fmt" + "io" + "net/http" + "strings" + "syscall" + + "github.com/smallnest/ringbuffer" + + "github.com/gpustack/gguf-parser-go/util/bytex" +) + +type SeekerFile struct { + cli *http.Client + req *http.Request + b *ringbuffer.RingBuffer + c int64 + l int64 +} + +// OpenSeekerFile tries the GET http.Request as a SeekerFile, +// and returns a SeekerFile, or an error if any. +func OpenSeekerFile(cli *http.Client, req *http.Request, opts ...*SeekerFileOption) (*SeekerFile, error) { + if cli == nil { + return nil, errors.New("client is nil") + } + if req == nil { + return nil, errors.New("request is nil") + } + if req.Method != http.MethodGet { + return nil, errors.New("request method is not GET") + } + + var o *SeekerFileOption + if len(opts) > 0 { + o = opts[0] + } else { + o = SeekerFileOptions() + } + if o.bufSize <= 0 { + o.bufSize = 4 * 1024 * 1024 // 4mb + } + + var l int64 + { + if !o.skipRangeDownloadDetect { + req := req.Clone(req.Context()) + req.Method = http.MethodHead + err := Do(cli, req, func(resp *http.Response) error { + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("stat: status code %d", resp.StatusCode) + } + if !strings.EqualFold(resp.Header.Get("Accept-Ranges"), "bytes") { + return fmt.Errorf("stat: not support range download") + } + l = resp.ContentLength + return nil + }) + if err != nil { + return nil, fmt.Errorf("stat: do head request: %w", err) + } + } else { + req := req.Clone(req.Context()) + err := Do(cli, req, func(resp *http.Response) error { + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("stat: status code %d", resp.StatusCode) + } + l = resp.ContentLength + return nil + }) + if err != nil { + return nil, fmt.Errorf("stat: do get request: %w", err) + } + } + switch sz := int64(o.size); { + case sz > l: + return nil, fmt.Errorf("size %d is greater than limit %d", o.size, l) + case sz <= 0: + default: + l = sz + } + } + + b := ringbuffer.New(o.bufSize).WithCancel(req.Context()) + return &SeekerFile{cli: cli, req: req, b: b, c: 1<<63 - 1, l: l}, nil +} + +func (f *SeekerFile) Close() error { + if f.b != nil { + f.b.CloseWriter() + } + return nil +} + +func (f *SeekerFile) Len() int64 { + return f.l +} + +func (f *SeekerFile) ReadAt(p []byte, off int64) (int, error) { + if off < 0 { + return 0, syscall.EINVAL + } + if off > f.Len() { + return 0, io.EOF + } + + // Sync and move to new offset, if backward or empty buffer. + if f.c > off || f.b.IsEmpty() { + if err := f.sync(off, true); err != nil { + return 0, err + } + } + + var ( + remain = int64(f.b.Length()) + capacity = int64(f.b.Capacity()) + need = int64(len(p)) + ) + + switch { + case f.c+remain >= off+need: // Skip and move to new offset, if enough to forward. + if err := f.skip(off - f.c); err != nil { + return 0, err + } + return f.Read(p) + case f.c+capacity >= off+need: // Sync and move to new offset, if enough to forward after synced. + if err := f.sync(f.c+remain, false); err != nil { + return 0, err + } + if err := f.skip(off - f.c); err != nil { + return 0, err + } + return f.Read(p) + default: + } + + // Otherwise, read directly. + + f.b.Reset() + f.c = off + + // Request remain needing. + lim := off + int64(len(p)) - 1 + if lim > f.Len() { + lim = f.Len() + } + req := f.req.Clone(f.req.Context()) + req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, lim)) + resp, err := f.cli.Do(req) + if err != nil { + return 0, err + } + defer Close(resp) + if resp.StatusCode != http.StatusPartialContent && resp.StatusCode != http.StatusOK { + return 0, errors.New(resp.Status) + } + n, err := resp.Body.Read(p) + f.c += int64(n) + return n, err +} + +func (f *SeekerFile) Read(p []byte) (int, error) { + n, err := f.b.Read(p) + f.c += int64(n) + return n, err +} + +func (f *SeekerFile) sync(off int64, reset bool) error { + lim := off + int64(f.b.Free()) - 1 + if lim > f.Len() { + lim = f.Len() + } + req := f.req.Clone(f.req.Context()) + req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", off, lim)) + + resp, err := f.cli.Do(req) + if err != nil { + return err + } + defer Close(resp) + if resp.StatusCode != http.StatusPartialContent && resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + + buf := bytex.GetBytes() + defer bytex.Put(buf) + if reset { + f.b.Reset() + f.c = off + } + + _, err = io.CopyBuffer(_WriterOnly{w: f.b}, resp.Body, buf) + if err != nil { + return err + } + + return nil +} + +func (f *SeekerFile) skip(dif int64) error { + if dif <= 0 { + return nil + } + + buf := bytex.GetBytes(uint64(dif)) + defer bytex.Put(buf) + n, err := f.b.Read(buf) + f.c += int64(n) + if err != nil { + return err + } + return nil +} + +// _WriterOnly is a wrapper to expose the io.Writer method only, +// which to avoid calling the io.ReaderFrom method. +type _WriterOnly struct { + w io.Writer +} + +func (w _WriterOnly) Write(p []byte) (int, error) { + return w.w.Write(p) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/file_options.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/file_options.go new file mode 100644 index 00000000..8cf4b038 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/file_options.go @@ -0,0 +1,61 @@ +package httpx + +type SeekerFileOption struct { + bufSize int + size int + skipRangeDownloadDetect bool +} + +func SeekerFileOptions() *SeekerFileOption { + return &SeekerFileOption{ + bufSize: 4 * 1024 * 1024, // 4mb + } +} + +// WithBufferSize sets the size of the buffer to read the file, +// +// Default is 4mb. +func (o *SeekerFileOption) WithBufferSize(bufSize int) *SeekerFileOption { + if o == nil || bufSize <= 0 { + return o + } + o.bufSize = bufSize + return o +} + +// WithSize sets the size of the file to read, +// +// If the size is greater than the content size of the file, it will return an error. +func (o *SeekerFileOption) WithSize(size int) *SeekerFileOption { + if o == nil || size <= 0 { + return o + } + o.size = size + return o +} + +// WithoutRangeDownloadDetect disables range download detection. +// +// Usually, OpenSeekerFile sends a "HEAD" HTTP request to destination to get the content size from the "Content-Length" header, +// and confirms whether supports range download via the "Accept-Ranges" header. +// However, some servers may not support the "HEAD" method, or the "Accept-Ranges" header is not set correctly. +// +// With this option, OpenSeekerFile sends "GET" HTTP request to get the content size as usual, +// and does not confirm whether supports range download. But during the seeking read, +// it still uses the "Range" header to read the file. +func (o *SeekerFileOption) WithoutRangeDownloadDetect() *SeekerFileOption { + if o == nil { + return o + } + o.skipRangeDownloadDetect = true + return o +} + +// If is a conditional option, +// which receives a boolean condition to trigger the given function or not. +func (o *SeekerFileOption) If(condition bool, then func(*SeekerFileOption) *SeekerFileOption) *SeekerFileOption { + if condition { + return then(o) + } + return o +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/proxy.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/proxy.go new file mode 100644 index 00000000..4abdece2 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/proxy.go @@ -0,0 +1,37 @@ +package httpx + +import ( + "net" + "net/http" + "net/url" + "strings" + + "github.com/gpustack/gguf-parser-go/util/osx" +) + +var noProxies []*net.IPNet + +func init() { + noProxyEnv := osx.Getenv("NO_PROXY", osx.Getenv("no_proxy")) + noProxyRules := strings.Split(noProxyEnv, ",") + for i := range noProxyRules { + _, cidr, _ := net.ParseCIDR(noProxyRules[i]) + if cidr != nil { + noProxies = append(noProxies, cidr) + } + } +} + +// ProxyFromEnvironment is similar to http.ProxyFromEnvironment, +// but it also respects the NO_PROXY environment variable. +func ProxyFromEnvironment(r *http.Request) (*url.URL, error) { + if ip := net.ParseIP(r.URL.Hostname()); ip != nil { + for i := range noProxies { + if noProxies[i].Contains(ip) { + return nil, nil + } + } + } + + return http.ProxyFromEnvironment(r) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/resolver.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/resolver.go new file mode 100644 index 00000000..42b10c3a --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/resolver.go @@ -0,0 +1,51 @@ +package httpx + +import ( + "context" + "net" + "time" + + "github.com/rs/dnscache" +) + +// DefaultResolver is the default DNS resolver used by the package, +// which caches DNS lookups in memory. +var DefaultResolver = &dnscache.Resolver{ + // NB(thxCode): usually, a high latency DNS is about 3s, + // so we set the timeout to 5s here. + Timeout: 5 * time.Second, + Resolver: net.DefaultResolver, +} + +func init() { + go func() { + t := time.NewTimer(5 * time.Minute) + defer t.Stop() + for range t.C { + DefaultResolver.RefreshWithOptions(dnscache.ResolverRefreshOptions{ + ClearUnused: true, + PersistOnFailure: false, + }) + } + }() +} + +func DNSCacheDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { + return func(ctx context.Context, nw, addr string) (conn net.Conn, err error) { + h, p, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + ips, err := DefaultResolver.LookupHost(ctx, h) + if err != nil { + return nil, err + } + for _, ip := range ips { + conn, err = dialer.DialContext(ctx, nw, net.JoinHostPort(ip, p)) + if err == nil { + break + } + } + return conn, err + } +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/transport.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/transport.go new file mode 100644 index 00000000..2e30eb62 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/transport.go @@ -0,0 +1,25 @@ +package httpx + +import ( + "net/http" +) + +// DefaultTransport is similar to the default http.DefaultTransport used by the package. +var DefaultTransport http.RoundTripper = Transport() + +// DefaultInsecureTransport is the default http.DefaultTransport used by the package, +// with TLS insecure skip verify. +var DefaultInsecureTransport http.RoundTripper = Transport(TransportOptions().WithoutInsecureVerify()) + +// Transport returns a new http.Transport with the given options, +// the result http.Transport is used for constructing http.Client. +func Transport(opts ...*TransportOption) *http.Transport { + var o *TransportOption + if len(opts) > 0 { + o = opts[0] + } else { + o = TransportOptions() + } + + return o.transport +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/httpx/transport_options.go b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/transport_options.go new file mode 100644 index 00000000..3ac9e59b --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/httpx/transport_options.go @@ -0,0 +1,202 @@ +package httpx + +import ( + "crypto/tls" + "net" + "net/http" + "net/url" + "time" +) + +type TransportOption struct { + dialer *net.Dialer + transport *http.Transport +} + +func TransportOptions() *TransportOption { + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + } + transport := &http.Transport{ + Proxy: ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + }, + DialContext: DNSCacheDialContext(dialer), + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + return &TransportOption{ + dialer: dialer, + transport: transport, + } +} + +// WithProxy sets the proxy. +func (o *TransportOption) WithProxy(proxy func(*http.Request) (*url.URL, error)) *TransportOption { + if o == nil || o.transport == nil { + return o + } + o.transport.Proxy = proxy + return o +} + +// WithoutProxy disables the proxy. +func (o *TransportOption) WithoutProxy() *TransportOption { + if o == nil || o.transport == nil { + return o + } + o.transport.Proxy = nil + return o +} + +// WithKeepalive sets the keepalive. +func (o *TransportOption) WithKeepalive(timeoutAndKeepalive ...time.Duration) *TransportOption { + if o == nil || o.transport == nil || o.dialer == nil { + return o + } + tak := [2]time.Duration{30 * time.Second, 30 * time.Second} + if len(timeoutAndKeepalive) > 0 { + tak[0] = timeoutAndKeepalive[0] + if len(timeoutAndKeepalive) > 1 { + tak[1] = timeoutAndKeepalive[1] + } + } + o.dialer.Timeout, o.dialer.KeepAlive = tak[0], tak[1] + o.transport.MaxIdleConns = 100 + o.transport.IdleConnTimeout = 90 * time.Second + return o +} + +// WithoutKeepalive disables the keepalive. +func (o *TransportOption) WithoutKeepalive() *TransportOption { + if o == nil || o.transport == nil { + return o + } + o.dialer.KeepAlive = -1 + o.transport.MaxIdleConns = 0 + o.transport.IdleConnTimeout = 0 + return o +} + +// WithInsecureVerify verifies the insecure connection. +func (o *TransportOption) WithInsecureVerify() *TransportOption { + if o == nil || o.transport == nil || o.transport.TLSClientConfig == nil { + return o + } + o.transport.TLSClientConfig.InsecureSkipVerify = false + return o +} + +// WithoutInsecureVerify skips the insecure connection verify. +func (o *TransportOption) WithoutInsecureVerify() *TransportOption { + if o == nil || o.transport == nil || o.transport.TLSClientConfig == nil { + return o + } + o.transport.TLSClientConfig.InsecureSkipVerify = true + return o +} + +// TimeoutForDial sets the timeout for network dial. +// +// This timeout controls the [network dial] only. +// +// Use 0 to disable timeout. +func (o *TransportOption) TimeoutForDial(timeout time.Duration) *TransportOption { + if o == nil || o.dialer == nil { + return o + } + o.dialer.Timeout = timeout + return o +} + +// TimeoutForResponseHeader sets the timeout for response header. +// +// This timeout controls the [response header reading] only. +// +// Use 0 to disable timeout. +func (o *TransportOption) TimeoutForResponseHeader(timeout time.Duration) *TransportOption { + if o == nil || o.transport == nil { + return o + } + o.transport.ResponseHeaderTimeout = timeout + return o +} + +// TimeoutForTLSHandshake sets the timeout for tls handshake. +// +// This timeout controls the [tls handshake] only. +// +// Use 0 to disable timeout. +func (o *TransportOption) TimeoutForTLSHandshake(timeout time.Duration) *TransportOption { + if o == nil || o.transport == nil { + return o + } + o.transport.TLSHandshakeTimeout = timeout + return o +} + +// TimeoutForIdleConn sets the timeout for idle connection. +// +// This timeout controls the [idle connection lifetime] only. +// +// Use 0 to disable timeout. +func (o *TransportOption) TimeoutForIdleConn(timeout time.Duration) *TransportOption { + if o == nil || o.transport == nil { + return o + } + o.transport.IdleConnTimeout = timeout + return o +} + +// WithTLSClientConfig sets the tls.Config. +func (o *TransportOption) WithTLSClientConfig(config *tls.Config) *TransportOption { + if o == nil || o.transport == nil { + return o + } + o.transport.TLSClientConfig = config + return o +} + +// WithoutDNSCache disables the dns cache. +func (o *TransportOption) WithoutDNSCache() *TransportOption { + if o == nil || o.transport == nil || o.dialer == nil { + return o + } + o.transport.DialContext = o.dialer.DialContext + return o +} + +// WithDialer sets the dialer. +func (o *TransportOption) WithDialer(dialer *net.Dialer) *TransportOption { + if o == nil || o.transport == nil || dialer == nil { + return o + } + o.dialer = dialer + o.transport.DialContext = DNSCacheDialContext(o.dialer) + return o +} + +// Customize sets the transport. +func (o *TransportOption) Customize(fn func(*http.Transport)) *TransportOption { + if o == nil || o.transport == nil { + return o + } + o.dialer = nil + fn(o.transport) + return o +} + +// If is a conditional option, +// which receives a boolean condition to trigger the given function or not. +func (o *TransportOption) If(condition bool, then func(*TransportOption) *TransportOption) *TransportOption { + if condition { + return then(o) + } + return o +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/json/common.go b/vendor/github.com/gpustack/gguf-parser-go/util/json/common.go new file mode 100644 index 00000000..57a54064 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/json/common.go @@ -0,0 +1,66 @@ +package json + +import ( + stdjson "encoding/json" + "fmt" +) + +type RawMessage = stdjson.RawMessage + +var ( + MarshalIndent = stdjson.MarshalIndent + Indent = stdjson.Indent + NewEncoder = stdjson.NewEncoder + Valid = stdjson.Valid +) + +// MustMarshal is similar to Marshal, +// but panics if found error. +func MustMarshal(v any) []byte { + bs, err := Marshal(v) + if err != nil { + panic(fmt.Errorf("error marshaling json: %w", err)) + } + + return bs +} + +// MustUnmarshal is similar to Unmarshal, +// but panics if found error. +func MustUnmarshal(data []byte, v any) { + err := Unmarshal(data, v) + if err != nil { + panic(fmt.Errorf("error unmarshaling json: %w", err)) + } +} + +// MustMarshalIndent is similar to MarshalIndent, +// but panics if found error. +func MustMarshalIndent(v any, prefix, indent string) []byte { + bs, err := MarshalIndent(v, prefix, indent) + if err != nil { + panic(fmt.Errorf("error marshaling indent json: %w", err)) + } + + return bs +} + +// ShouldMarshal is similar to Marshal, +// but never return error. +func ShouldMarshal(v any) []byte { + bs, _ := Marshal(v) + return bs +} + +// ShouldUnmarshal is similar to Unmarshal, +// but never return error. +func ShouldUnmarshal(data []byte, v any) { + _ = Unmarshal(data, v) +} + +// ShouldMarshalIndent is similar to MarshalIndent, +// but never return error. +func ShouldMarshalIndent(v any, prefix, indent string) []byte { + bs, _ := MarshalIndent(v, prefix, indent) + return bs +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/json/jsoniter.go b/vendor/github.com/gpustack/gguf-parser-go/util/json/jsoniter.go new file mode 100644 index 00000000..edb2af67 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/json/jsoniter.go @@ -0,0 +1,47 @@ +//go:build !stdjson + +package json + +import ( + stdjson "encoding/json" + "strconv" + "unsafe" + + jsoniter "github.com/json-iterator/go" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +func init() { + // borrowed from https://github.com/json-iterator/go/issues/145#issuecomment-323483602 + decodeNumberAsInt64IfPossible := func(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + switch iter.WhatIsNext() { + case jsoniter.NumberValue: + var number stdjson.Number + + iter.ReadVal(&number) + i, err := strconv.ParseInt(string(number), 10, 64) + + if err == nil { + *(*any)(ptr) = i + return + } + + f, err := strconv.ParseFloat(string(number), 64) + if err == nil { + *(*any)(ptr) = f + return + } + default: + *(*any)(ptr) = iter.Read() + } + } + jsoniter.RegisterTypeDecoderFunc("interface {}", decodeNumberAsInt64IfPossible) + jsoniter.RegisterTypeDecoderFunc("any", decodeNumberAsInt64IfPossible) +} + +var ( + Marshal = json.Marshal + Unmarshal = json.Unmarshal + NewDecoder = json.NewDecoder +) diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/json/stdjson.go b/vendor/github.com/gpustack/gguf-parser-go/util/json/stdjson.go new file mode 100644 index 00000000..d04966ef --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/json/stdjson.go @@ -0,0 +1,13 @@ +//go:build stdjson + +package json + +import ( + "encoding/json" +) + +var ( + Marshal = json.Marshal + Unmarshal = json.Unmarshal + NewDecoder = json.NewDecoder +) diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/env.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/env.go new file mode 100644 index 00000000..553aa45a --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/env.go @@ -0,0 +1,29 @@ +package osx + +import ( + "os" +) + +// ExistEnv checks if the environment variable named by the key exists. +func ExistEnv(key string) bool { + _, ok := os.LookupEnv(key) + return ok +} + +// Getenv retrieves the value of the environment variable named by the key. +// It returns the default, which will be empty if the variable is not present. +// To distinguish between an empty value and an unset value, use LookupEnv. +func Getenv(key string, def ...string) string { + e, ok := os.LookupEnv(key) + if !ok && len(def) != 0 { + return def[0] + } + + return e +} + +// ExpandEnv is similar to Getenv, +// but replaces ${var} or $var in the result. +func ExpandEnv(key string, def ...string) string { + return os.ExpandEnv(Getenv(key, def...)) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/file.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file.go new file mode 100644 index 00000000..93448147 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file.go @@ -0,0 +1,134 @@ +package osx + +import ( + "io" + "os" + "path/filepath" + "strings" +) + +// InlineTilde replaces the leading ~ with the home directory. +func InlineTilde(path string) string { + if path == "" { + return path + } + if strings.HasPrefix(path, "~"+string(filepath.Separator)) { + hd, err := os.UserHomeDir() + if err == nil { + path = filepath.Join(hd, path[2:]) + } + } + return path +} + +// Open is similar to os.Open but supports ~ as the home directory. +func Open(path string) (*os.File, error) { + p := filepath.Clean(path) + p = InlineTilde(p) + return os.Open(p) +} + +// Exists checks if the given path exists. +func Exists(path string, checks ...func(os.FileInfo) bool) bool { + p := filepath.Clean(path) + p = InlineTilde(p) + + stat, err := os.Lstat(p) + if err != nil { + return false + } + + for i := range checks { + if checks[i] == nil { + continue + } + + if !checks[i](stat) { + return false + } + } + + return true +} + +// ExistsDir checks if the given path exists and is a directory. +func ExistsDir(path string) bool { + return Exists(path, func(stat os.FileInfo) bool { + return stat.Mode().IsDir() + }) +} + +// ExistsLink checks if the given path exists and is a symbolic link. +func ExistsLink(path string) bool { + return Exists(path, func(stat os.FileInfo) bool { + return stat.Mode()&os.ModeSymlink != 0 + }) +} + +// ExistsFile checks if the given path exists and is a regular file. +func ExistsFile(path string) bool { + return Exists(path, func(stat os.FileInfo) bool { + return stat.Mode().IsRegular() + }) +} + +// ExistsSocket checks if the given path exists and is a socket. +func ExistsSocket(path string) bool { + return Exists(path, func(stat os.FileInfo) bool { + return stat.Mode()&os.ModeSocket != 0 + }) +} + +// ExistsDevice checks if the given path exists and is a device. +func ExistsDevice(path string) bool { + return Exists(path, func(stat os.FileInfo) bool { + return stat.Mode()&os.ModeDevice != 0 + }) +} + +// Close closes the given io.Closer without error. +func Close(c io.Closer) { + if c == nil { + return + } + _ = c.Close() +} + +// WriteFile is similar to os.WriteFile but supports ~ as the home directory, +// and also supports the parent directory creation. +func WriteFile(name string, data []byte, perm os.FileMode) error { + p := filepath.Clean(name) + p = InlineTilde(p) + + if err := os.MkdirAll(filepath.Dir(p), 0o700); err != nil { + return err + } + + return os.WriteFile(p, data, perm) +} + +// CreateFile is similar to os.Create but supports ~ as the home directory, +// and also supports the parent directory creation. +func CreateFile(name string, perm os.FileMode) (*os.File, error) { + p := filepath.Clean(name) + p = InlineTilde(p) + + if err := os.MkdirAll(filepath.Dir(p), 0o700); err != nil { + return nil, err + } + + return os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm) +} + +// OpenFile is similar to os.OpenFile but supports ~ as the home directory, +// and also supports the parent directory creation. +func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) { + p := filepath.Clean(name) + p = InlineTilde(p) + + if err := os.MkdirAll(filepath.Dir(p), 0o700); err != nil { + return nil, err + } + + return os.OpenFile(p, flag, perm) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap.go new file mode 100644 index 00000000..06812caa --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap.go @@ -0,0 +1,102 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package osx + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + "runtime/debug" + "syscall" +) + +type MmapFile struct { + f *os.File + b []byte +} + +func OpenMmapFile(path string) (*MmapFile, error) { + return OpenMmapFileWithSize(path, 0) +} + +func OpenMmapFileWithSize(path string, size int) (*MmapFile, error) { + p := filepath.Clean(path) + p = InlineTilde(p) + + f, err := os.Open(p) + if err != nil { + return nil, fmt.Errorf("try lock file: %w", err) + } + if size <= 0 { + info, err := f.Stat() + if err != nil { + Close(f) + return nil, fmt.Errorf("stat: %w", err) + } + size = int(info.Size()) + } + + b, err := mmap(f, size) + if err != nil { + Close(f) + return nil, fmt.Errorf("mmap, size %d: %w", size, err) + } + + return &MmapFile{f: f, b: b}, nil +} + +func (f *MmapFile) Close() error { + err0 := munmap(f.b) + err1 := f.f.Close() + + if err0 != nil { + return err0 + } + return err1 +} + +func (f *MmapFile) Bytes() []byte { + return f.b +} + +func (f *MmapFile) Len() int64 { + return int64(len(f.b)) +} + +var ErrPageFault = errors.New("page fault occurred while reading from memory map") + +func (f *MmapFile) ReadAt(p []byte, off int64) (_ int, err error) { + if off < 0 { + return 0, syscall.EINVAL + } + if off > f.Len() { + return 0, io.EOF + } + + old := debug.SetPanicOnFault(true) + defer func() { + debug.SetPanicOnFault(old) + if recover() != nil { + err = ErrPageFault + } + }() + + n := copy(p, f.b[off:]) + if n < len(p) { + err = io.EOF + } + return n, err +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_js.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_js.go new file mode 100644 index 00000000..9172a883 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_js.go @@ -0,0 +1,27 @@ +// Copyright 2022 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package osx + +import ( + "errors" + "os" +) + +func mmap(f *os.File, length int) ([]byte, error) { + return nil, errors.New("unsupported") +} + +func munmap(b []byte) (err error) { + return errors.New("unsupported") +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_unix.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_unix.go new file mode 100644 index 00000000..18725bbe --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_unix.go @@ -0,0 +1,30 @@ +// Copyright 2017 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package osx + +import ( + "os" + + "golang.org/x/sys/unix" +) + +func mmap(f *os.File, length int) ([]byte, error) { + return unix.Mmap(int(f.Fd()), 0, length, unix.PROT_READ, unix.MAP_SHARED) +} + +func munmap(b []byte) (err error) { + return unix.Munmap(b) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows.go new file mode 100644 index 00000000..b9879fc0 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows.go @@ -0,0 +1,33 @@ +package osx + +import ( + "os" + "syscall" + "unsafe" +) + +func mmap(f *os.File, size int) ([]byte, error) { + low, high := uint32(size), uint32(size>>32) + h, errno := syscall.CreateFileMapping(syscall.Handle(f.Fd()), nil, syscall.PAGE_READONLY, high, low, nil) + if h == 0 { + return nil, os.NewSyscallError("CreateFileMapping", errno) + } + + addr, errno := syscall.MapViewOfFile(h, syscall.FILE_MAP_READ, 0, 0, uintptr(size)) + if addr == 0 { + return nil, os.NewSyscallError("MapViewOfFile", errno) + } + + if err := syscall.CloseHandle(h); err != nil { + return nil, os.NewSyscallError("CloseHandle", err) + } + + return (*[maxMapSize]byte)(unsafe.Pointer(uintptr(addr)))[:size], nil +} + +func munmap(b []byte) error { + if err := syscall.UnmapViewOfFile((uintptr)(unsafe.Pointer(&b[0]))); err != nil { + return os.NewSyscallError("UnmapViewOfFile", err) + } + return nil +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows_386.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows_386.go new file mode 100644 index 00000000..aab64c34 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows_386.go @@ -0,0 +1,16 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package osx + +const maxMapSize = 0x7FFFFFFF // 2GB diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows_non386.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows_non386.go new file mode 100644 index 00000000..c1940706 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/file_mmap_windows_non386.go @@ -0,0 +1,18 @@ +// Copyright 2018 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build windows && !386 + +package osx + +const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/osx/homedir.go b/vendor/github.com/gpustack/gguf-parser-go/util/osx/homedir.go new file mode 100644 index 00000000..4482d1ea --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/osx/homedir.go @@ -0,0 +1,17 @@ +package osx + +import ( + "os" + "path/filepath" + "time" +) + +// UserHomeDir is similar to os.UserHomeDir, +// but returns the temp dir if the home dir is not found. +func UserHomeDir() string { + hd, err := os.UserHomeDir() + if err != nil { + hd = filepath.Join(os.TempDir(), time.Now().Format(time.DateOnly)) + } + return hd +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/ptr/pointer.go b/vendor/github.com/gpustack/gguf-parser-go/util/ptr/pointer.go new file mode 100644 index 00000000..b7f1cc4d --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/ptr/pointer.go @@ -0,0 +1,163 @@ +package ptr + +import ( + "time" + + "golang.org/x/exp/constraints" +) + +func Int(v int) *int { + return Ref(v) +} + +func IntDeref(v *int, def int) int { + return Deref(v, def) +} + +func Int8(v int8) *int8 { + return Ref(v) +} + +func Int8Deref(v *int8, def int8) int8 { + return Deref(v, def) +} + +func Int16(v int16) *int16 { + return Ref(v) +} + +func Int16Deref(v *int16, def int16) int16 { + return Deref(v, def) +} + +func Int32(v int32) *int32 { + return Ref(v) +} + +func Int32Deref(v *int32, def int32) int32 { + return Deref(v, def) +} + +func Int64(v int64) *int64 { + return Ref(v) +} + +func Int64Deref(v *int64, def int64) int64 { + return Deref(v, def) +} + +func Uint(v uint) *uint { + return Ref(v) +} + +func UintDeref(v *uint, def uint) uint { + return Deref(v, def) +} + +func Uint8(v uint8) *uint8 { + return Ref(v) +} + +func Uint8Deref(v *uint8, def uint8) uint8 { + return Deref(v, def) +} + +func Uint16(v uint16) *uint16 { + return Ref(v) +} + +func Uint16Deref(v *uint16, def uint16) uint16 { + return Deref(v, def) +} + +func Uint32(v uint32) *uint32 { + return Ref(v) +} + +func Uint32Deref(v *uint32, def uint32) uint32 { + return Deref(v, def) +} + +func Uint64(v uint64) *uint64 { + return Ref(v) +} + +func Uint64Deref(v *uint64, def uint64) uint64 { + return Deref(v, def) +} + +func Float32(v float32) *float32 { + return Ref(v) +} + +func Float32Deref(v *float32, def float32) float32 { + return Deref(v, def) +} + +func Float64(v float64) *float64 { + return Ref(v) +} + +func Float64Deref(v *float64, def float64) float64 { + return Deref(v, def) +} + +func String(v string) *string { + return Ref(v) +} + +func StringDeref(v *string, def string) string { + return Deref(v, def) +} + +func Bool(v bool) *bool { + return Ref(v) +} + +func BoolDeref(v *bool, def bool) bool { + return Deref(v, def) +} + +func Duration(v time.Duration) *time.Duration { + return Ref(v) +} + +func DurationDeref(v *time.Duration, def time.Duration) time.Duration { + return Deref(v, def) +} + +func Time(v time.Time) *time.Time { + return Ref(v) +} + +func TimeDeref(v *time.Time, def time.Time) time.Time { + return Deref(v, def) +} + +type Pointerable interface { + constraints.Ordered | ~bool | time.Time +} + +func Ref[T Pointerable](v T) *T { + return &v +} + +func To[T Pointerable](v T) *T { + return Ref(v) +} + +func Deref[T Pointerable](ptr *T, def T) T { + if ptr != nil { + return *ptr + } + + return def +} + +func Equal[T Pointerable](a, b *T) bool { + if a != nil && b != nil { + return *a == *b + } + + return false +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/slicex/search.go b/vendor/github.com/gpustack/gguf-parser-go/util/slicex/search.go new file mode 100644 index 00000000..28875118 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/slicex/search.go @@ -0,0 +1,17 @@ +package slicex + +import "golang.org/x/exp/constraints" + +// UpperBound returns an index of the first element that is greater than value. +func UpperBound[T constraints.Integer | constraints.Float](s []T, e T) int { + l, r := 0, len(s) + for l < r { + m := l + (r-l)/2 + if s[m] <= e { + l = m + 1 + } else { + r = m + } + } + return l +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/stringx/bytes.go b/vendor/github.com/gpustack/gguf-parser-go/util/stringx/bytes.go new file mode 100644 index 00000000..55f0477f --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/stringx/bytes.go @@ -0,0 +1,14 @@ +package stringx + +import "unsafe" + +// FromBytes converts a byte slice to a string. +func FromBytes(b *[]byte) string { + return unsafe.String(unsafe.SliceData(*b), len(*b)) +} + +// ToBytes converts a string to a byte slice, +// which is impossible to modify the item of slice. +func ToBytes(s *string) (bs []byte) { + return unsafe.Slice(unsafe.StringData(*s), len(*s)) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/stringx/random.go b/vendor/github.com/gpustack/gguf-parser-go/util/stringx/random.go new file mode 100644 index 00000000..06649810 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/stringx/random.go @@ -0,0 +1,61 @@ +package stringx + +// Borrowed from github.com/thanhpk/randstr. + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "encoding/hex" +) + +// list of default letters that can be used to make a random string when calling RandomString +// function with no letters provided. +var defLetters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + +// RandomBytes generates n random bytes. +func RandomBytes(n int) []byte { + b := make([]byte, n) + + _, err := rand.Read(b) + if err != nil { + panic(err) + } + + return b +} + +// RandomHex generates a random hex string with length of n +// e.g: 67aab2d956bd7cc621af22cfb169cba8. +func RandomHex(n int) string { return hex.EncodeToString(RandomBytes(n)) } + +// RandomString generates a random string using only letters provided in the letters parameter +// if user omit letters parameters, this function will use defLetters instead. +func RandomString(n int, letters ...string) string { + var ( + letterRunes []rune + bb bytes.Buffer + ) + + if len(letters) == 0 { + letterRunes = defLetters + } else { + letterRunes = []rune(letters[0]) + } + + bb.Grow(n) + + l := uint32(len(letterRunes)) + // On each loop, generate one random rune and append to output. + for i := 0; i < n; i++ { + bb.WriteRune(letterRunes[binary.BigEndian.Uint32(RandomBytes(4))%l]) + } + + return bb.String() +} + +// RandomBase64 generates a random base64 string with length of n, +// safe for URL. +func RandomBase64(n int) string { + return RandomString(n, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_") +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/stringx/strings.go b/vendor/github.com/gpustack/gguf-parser-go/util/stringx/strings.go new file mode 100644 index 00000000..3b15eab8 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/stringx/strings.go @@ -0,0 +1,46 @@ +package stringx + +import "strings" + +// CutFromLeft is the same as strings.Cut, +// which starts from left to right, +// slices s around the first instance of sep, +// returning the text before and after sep. +// The found result reports whether sep appears in s. +// If sep does not appear in s, cut returns s, "", false. +func CutFromLeft(s, sep string) (before, after string, found bool) { + return strings.Cut(s, sep) +} + +// CutFromRight takes the same arguments as CutFromLeft, +// but starts from right to left, +// slices s around the last instance of sep, +// return the text before and after sep. +// The found result reports whether sep appears in s. +// If sep does not appear in s, cut returns s, "", false. +func CutFromRight(s, sep string) (before, after string, found bool) { + if i := strings.LastIndex(s, sep); i >= 0 { + return s[:i], s[i+len(sep):], true + } + return s, "", false +} + +// ReplaceAllFunc is similar to strings.ReplaceAll, +// but it replaces each rune in s with the result of f(r). +func ReplaceAllFunc(s string, f func(rune) rune) string { + var b strings.Builder + for _, r := range s { + b.WriteRune(f(r)) + } + return b.String() +} + +// HasSuffixes checks if s has any of the suffixes in prefixes. +func HasSuffixes(s string, suffixes ...string) bool { + for _, suffix := range suffixes { + if strings.HasSuffix(s, suffix) { + return true + } + } + return false +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/util/stringx/sum.go b/vendor/github.com/gpustack/gguf-parser-go/util/stringx/sum.go new file mode 100644 index 00000000..8f60031d --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/util/stringx/sum.go @@ -0,0 +1,85 @@ +package stringx + +import ( + "crypto/sha256" + "encoding/hex" + "hash/fnv" +) + +// SumByFNV64a sums up the string(s) by FNV-64a hash algorithm. +func SumByFNV64a(s string, ss ...string) string { + h := fnv.New64a() + + _, _ = h.Write(ToBytes(&s)) + for i := range ss { + _, _ = h.Write(ToBytes(&ss[i])) + } + + sum := h.Sum(nil) + return hex.EncodeToString(sum) +} + +// SumBytesByFNV64a sums up the byte slice(s) by FNV-64a hash algorithm. +func SumBytesByFNV64a(bs []byte, bss ...[]byte) string { + h := fnv.New64a() + + _, _ = h.Write(bs) + for i := range bss { + _, _ = h.Write(bss[i]) + } + + sum := h.Sum(nil) + return hex.EncodeToString(sum) +} + +// SumBySHA256 sums up the string(s) by SHA256 hash algorithm. +func SumBySHA256(s string, ss ...string) string { + h := sha256.New() + + _, _ = h.Write(ToBytes(&s)) + for i := range ss { + _, _ = h.Write(ToBytes(&ss[i])) + } + + sum := h.Sum(nil) + return hex.EncodeToString(sum) +} + +// SumBytesBySHA256 sums up the byte slice(s) by SHA256 hash algorithm. +func SumBytesBySHA256(bs []byte, bss ...[]byte) string { + h := sha256.New() + + _, _ = h.Write(bs) + for i := range bss { + _, _ = h.Write(bss[i]) + } + + sum := h.Sum(nil) + return hex.EncodeToString(sum) +} + +// SumBySHA224 sums up the string(s) by SHA224 hash algorithm. +func SumBySHA224(s string, ss ...string) string { + h := sha256.New224() + + _, _ = h.Write(ToBytes(&s)) + for i := range ss { + _, _ = h.Write(ToBytes(&ss[i])) + } + + sum := h.Sum(nil) + return hex.EncodeToString(sum) +} + +// SumBytesBySHA224 sums up the byte slice(s) by SHA224 hash algorithm. +func SumBytesBySHA224(bs []byte, bss ...[]byte) string { + h := sha256.New224() + + _, _ = h.Write(bs) + for i := range bss { + _, _ = h.Write(bss[i]) + } + + sum := h.Sum(nil) + return hex.EncodeToString(sum) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/zz_generated.diffusion_model_memory_usage.regression.go b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.diffusion_model_memory_usage.regression.go new file mode 100644 index 00000000..e2ab5189 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.diffusion_model_memory_usage.regression.go @@ -0,0 +1,130 @@ +package gguf_parser + +import "math" + +// GuessSD1DiffusionModelMemoryUsage returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func GuessSD1DiffusionModelMemoryUsage(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{7876368.5672, 161.4230198633, 0.0078124893} + degree := 2 + x := float64(width * height) + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} + +// GuessSD2DiffusionModelMemoryUsage returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func GuessSD2DiffusionModelMemoryUsage(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{-355043979.0562, -1193.3271458642, 0.0054023818} + degree := 2 + x := float64(width * height) + + if flashAttention { + coefficients = []float64{3780681.28078, 513.2102510935} + degree = 1 + } + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} + +// GuessSDXLDiffusionModelMemoryUsage returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func GuessSDXLDiffusionModelMemoryUsage(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{55541290.3893, 138.3196116655, 0.0006109455} + degree := 2 + x := float64(width * height) + + if flashAttention { + coefficients = []float64{-5958802.78052, 500.0687898915} + degree = 1 + } + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} + +// GuessSDXLRefinerDiffusionModelMemoryUsage returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func GuessSDXLRefinerDiffusionModelMemoryUsage(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{49395992.3449, 155.2477810191, 0.0007351736} + degree := 2 + x := float64(width * height) + + if flashAttention { + coefficients = []float64{7031343.31998, 599.4137437227} + degree = 1 + } + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} + +// GuessSD3MediumDiffusionModelMemoryUsage returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func GuessSD3MediumDiffusionModelMemoryUsage(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{16529921.3700, 234.6656247718, 0.0014648995} + degree := 2 + x := float64(width * height) + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} + +// GuessSD35MediumDiffusionModelMemoryUsage returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func GuessSD35MediumDiffusionModelMemoryUsage(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{17441103.4726, 281.6956819806, 0.0014651233} + degree := 2 + x := float64(width * height) + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} + +// GuessSD35LargeDiffusionModelMemoryUsage returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func GuessSD35LargeDiffusionModelMemoryUsage(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{23204369.2029, 410.3731196298, 0.0023195947} + degree := 2 + x := float64(width * height) + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} + +// GuessFLUXDiffusionModelMemoryUsage returns the memory usage in bytes for the given width and height, +// which is calculated by linear regression or polynomial regression. +func GuessFLUXDiffusionModelMemoryUsage(width, height uint32, flashAttention bool) uint64 { + coefficients := []float64{46511668.6742, 997.7758807792, 0.0014573393} + degree := 2 + x := float64(width * height) + + y := float64(0) + for i := 0; i <= degree; i++ { + y += coefficients[i] * math.Pow(x, float64(i)) + } + return uint64(y) +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggmltype.stringer.go b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggmltype.stringer.go new file mode 100644 index 00000000..3eaad12f --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggmltype.stringer.go @@ -0,0 +1,62 @@ +// Code generated by "stringer -linecomment -type GGMLType -output zz_generated.ggmltype.stringer.go -trimprefix GGMLType"; DO NOT EDIT. + +package gguf_parser + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GGMLTypeF32-0] + _ = x[GGMLTypeF16-1] + _ = x[GGMLTypeQ4_0-2] + _ = x[GGMLTypeQ4_1-3] + _ = x[GGMLTypeQ4_2-4] + _ = x[GGMLTypeQ4_3-5] + _ = x[GGMLTypeQ5_0-6] + _ = x[GGMLTypeQ5_1-7] + _ = x[GGMLTypeQ8_0-8] + _ = x[GGMLTypeQ8_1-9] + _ = x[GGMLTypeQ2_K-10] + _ = x[GGMLTypeQ3_K-11] + _ = x[GGMLTypeQ4_K-12] + _ = x[GGMLTypeQ5_K-13] + _ = x[GGMLTypeQ6_K-14] + _ = x[GGMLTypeQ8_K-15] + _ = x[GGMLTypeIQ2_XXS-16] + _ = x[GGMLTypeIQ2_XS-17] + _ = x[GGMLTypeIQ3_XXS-18] + _ = x[GGMLTypeIQ1_S-19] + _ = x[GGMLTypeIQ4_NL-20] + _ = x[GGMLTypeIQ3_S-21] + _ = x[GGMLTypeIQ2_S-22] + _ = x[GGMLTypeIQ4_XS-23] + _ = x[GGMLTypeI8-24] + _ = x[GGMLTypeI16-25] + _ = x[GGMLTypeI32-26] + _ = x[GGMLTypeI64-27] + _ = x[GGMLTypeF64-28] + _ = x[GGMLTypeIQ1_M-29] + _ = x[GGMLTypeBF16-30] + _ = x[GGMLTypeQ4_0_4_4-31] + _ = x[GGMLTypeQ4_0_4_8-32] + _ = x[GGMLTypeQ4_0_8_8-33] + _ = x[GGMLTypeTQ1_0-34] + _ = x[GGMLTypeTQ2_0-35] + _ = x[GGMLTypeIQ4_NL_4_4-36] + _ = x[GGMLTypeIQ4_NL_4_8-37] + _ = x[GGMLTypeIQ4_NL_8_8-38] + _ = x[_GGMLTypeCount-39] +} + +const _GGMLType_name = "F32F16Q4_0Q4_1Q4_2Q4_3Q5_0Q5_1Q8_0Q8_1Q2_KQ3_KQ4_KQ5_KQ6_KQ8_KIQ2_XXSIQ2_XSIQ3_XXSIQ1_SIQ4_NLIQ3_SIQ2_SIQ4_XSI8I16I32I64F64IQ1_MBF16Q4_0_4_4Q4_0_4_8Q4_0_8_8TQ1_0TQ2_0IQ4_NL_4_4IQ4_NL_4_8IQ4_NL_8_8Unknown" + +var _GGMLType_index = [...]uint8{0, 3, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 69, 75, 82, 87, 93, 98, 103, 109, 111, 114, 117, 120, 123, 128, 132, 140, 148, 156, 161, 166, 176, 186, 196, 203} + +func (i GGMLType) String() string { + if i >= GGMLType(len(_GGMLType_index)-1) { + return "GGMLType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _GGMLType_name[_GGMLType_index[i]:_GGMLType_index[i+1]] +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/zz_generated.gguffiletype.stringer.go b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.gguffiletype.stringer.go new file mode 100644 index 00000000..a6abaa22 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.gguffiletype.stringer.go @@ -0,0 +1,56 @@ +// Code generated by "stringer -linecomment -type GGUFFileType -output zz_generated.gguffiletype.stringer.go -trimprefix GGUFFileType"; DO NOT EDIT. + +package gguf_parser + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GGUFFileTypeAllF32-0] + _ = x[GGUFFileTypeMostlyF16-1] + _ = x[GGUFFileTypeMostlyQ4_0-2] + _ = x[GGUFFileTypeMostlyQ4_1-3] + _ = x[GGUFFileTypeMostlyQ4_1_F16-4] + _ = x[GGUFFileTypeMostlyQ4_2-5] + _ = x[GGUFFileTypeMostlyQ4_3-6] + _ = x[GGUFFileTypeMostlyQ8_0-7] + _ = x[GGUFFileTypeMostlyQ5_0-8] + _ = x[GGUFFileTypeMostlyQ5_1-9] + _ = x[GGUFFileTypeMostlyQ2_K-10] + _ = x[GGUFFileTypeMostlyQ3_K-11] + _ = x[GGUFFileTypeMostlyQ4_K-12] + _ = x[GGUFFileTypeMostlyQ5_K-13] + _ = x[GGUFFileTypeMostlyQ6_K-14] + _ = x[GGUFFileTypeMostlyIQ2_XXS-15] + _ = x[GGUFFileTypeMostlyIQ2_XS-16] + _ = x[GGUFFileTypeMostlyIQ3_XXS-17] + _ = x[GGUFFileTypeMostlyIQ1_S-18] + _ = x[GGUFFileTypeMostlyIQ4_NL-19] + _ = x[GGUFFileTypeMostlyIQ3_S-20] + _ = x[GGUFFileTypeMostlyIQ2_S-21] + _ = x[GGUFFileTypeMostlyIQ4_XS-22] + _ = x[GGUFFileTypeMostlyIQ1_M-23] + _ = x[GGUFFileTypeMostlyBF16-24] + _ = x[GGUFFileTypeMostlyQ4_0_4_4-25] + _ = x[GGUFFileTypeMostlyQ4_0_4_8-26] + _ = x[GGUFFileTypeMostlyQ4_0_8_8-27] + _ = x[GGUFFileTypeMostlyTQ1_0-28] + _ = x[GGUFFileTypeMostlyTQ2_0-29] + _ = x[GGUFFileTypeMostlyIQ4_NL_4_4-30] + _ = x[GGUFFileTypeMostlyIQ4_NL_4_8-31] + _ = x[GGUFFileTypeMostlyIQ4_NL_8_8-32] + _ = x[_GGUFFileTypeCount-33] +} + +const _GGUFFileType_name = "F32F16Q4_0Q4_1Q4_1_F16Q4_2Q4_3Q8_0Q5_0Q5_1Q2_KQ3_K/Q3_K_SQ4_K/Q3_K_MQ5_K/Q3_K_LQ6_K/Q4_K_SIQ2_XXS/Q4_K_MIQ2_XS/Q5_K_SIQ3_XXS/Q5_K_MIQ1_S/Q6_KIQ4_NLIQ3_SIQ2_SIQ4_XSIQ1_MBF16Q4_0_4x4Q4_0_4x8Q4_0_8x8TQ1_0TQ2_0IQ4_NL_4x4IQ4_NL_4x8IQ4_NL_8x8Unknown" + +var _GGUFFileType_index = [...]uint8{0, 3, 6, 10, 14, 22, 26, 30, 34, 38, 42, 46, 57, 68, 79, 90, 104, 117, 131, 141, 147, 152, 157, 163, 168, 172, 180, 188, 196, 201, 206, 216, 226, 236, 243} + +func (i GGUFFileType) String() string { + if i >= GGUFFileType(len(_GGUFFileType_index)-1) { + return "GGUFFileType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _GGUFFileType_name[_GGUFFileType_index[i]:_GGUFFileType_index[i+1]] +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufmagic.stringer.go b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufmagic.stringer.go new file mode 100644 index 00000000..e6227e40 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufmagic.stringer.go @@ -0,0 +1,41 @@ +// Code generated by "stringer -linecomment -type GGUFMagic -output zz_generated.ggufmagic.stringer.go -trimprefix GGUFMagic"; DO NOT EDIT. + +package gguf_parser + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GGUFMagicGGML-1734831468] + _ = x[GGUFMagicGGMF-1734831462] + _ = x[GGUFMagicGGJT-1734830708] + _ = x[GGUFMagicGGUFLe-1179993927] + _ = x[GGUFMagicGGUFBe-1195857222] +} + +const ( + _GGUFMagic_name_0 = "GGUF" + _GGUFMagic_name_1 = "GGUF" + _GGUFMagic_name_2 = "GGJT" + _GGUFMagic_name_3 = "GGMF" + _GGUFMagic_name_4 = "GGML" +) + +func (i GGUFMagic) String() string { + switch { + case i == 1179993927: + return _GGUFMagic_name_0 + case i == 1195857222: + return _GGUFMagic_name_1 + case i == 1734830708: + return _GGUFMagic_name_2 + case i == 1734831462: + return _GGUFMagic_name_3 + case i == 1734831468: + return _GGUFMagic_name_4 + default: + return "GGUFMagic(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufmetadatavaluetype.stringer.go b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufmetadatavaluetype.stringer.go new file mode 100644 index 00000000..78760c6d --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufmetadatavaluetype.stringer.go @@ -0,0 +1,36 @@ +// Code generated by "stringer -linecomment -type GGUFMetadataValueType -output zz_generated.ggufmetadatavaluetype.stringer.go -trimprefix GGUFMetadataValueType"; DO NOT EDIT. + +package gguf_parser + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GGUFMetadataValueTypeUint8-0] + _ = x[GGUFMetadataValueTypeInt8-1] + _ = x[GGUFMetadataValueTypeUint16-2] + _ = x[GGUFMetadataValueTypeInt16-3] + _ = x[GGUFMetadataValueTypeUint32-4] + _ = x[GGUFMetadataValueTypeInt32-5] + _ = x[GGUFMetadataValueTypeFloat32-6] + _ = x[GGUFMetadataValueTypeBool-7] + _ = x[GGUFMetadataValueTypeString-8] + _ = x[GGUFMetadataValueTypeArray-9] + _ = x[GGUFMetadataValueTypeUint64-10] + _ = x[GGUFMetadataValueTypeInt64-11] + _ = x[GGUFMetadataValueTypeFloat64-12] + _ = x[_GGUFMetadataValueTypeCount-13] +} + +const _GGUFMetadataValueType_name = "Uint8Int8Uint16Int16Uint32Int32Float32BoolStringArrayUint64Int64Float64Unknown" + +var _GGUFMetadataValueType_index = [...]uint8{0, 5, 9, 15, 20, 26, 31, 38, 42, 48, 53, 59, 64, 71, 78} + +func (i GGUFMetadataValueType) String() string { + if i >= GGUFMetadataValueType(len(_GGUFMetadataValueType_index)-1) { + return "GGUFMetadataValueType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _GGUFMetadataValueType_name[_GGUFMetadataValueType_index[i]:_GGUFMetadataValueType_index[i+1]] +} diff --git a/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufversion.stringer.go b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufversion.stringer.go new file mode 100644 index 00000000..a54ffe96 --- /dev/null +++ b/vendor/github.com/gpustack/gguf-parser-go/zz_generated.ggufversion.stringer.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -linecomment -type GGUFVersion -output zz_generated.ggufversion.stringer.go -trimprefix GGUFVersion"; DO NOT EDIT. + +package gguf_parser + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[GGUFVersionV1-1] + _ = x[GGUFVersionV2-2] + _ = x[GGUFVersionV3-3] +} + +const _GGUFVersion_name = "V1V2V3" + +var _GGUFVersion_index = [...]uint8{0, 2, 4, 6} + +func (i GGUFVersion) String() string { + i -= 1 + if i >= GGUFVersion(len(_GGUFVersion_index)-1) { + return "GGUFVersion(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _GGUFVersion_name[_GGUFVersion_index[i]:_GGUFVersion_index[i+1]] +} diff --git a/vendor/github.com/henvic/httpretty/.gitignore b/vendor/github.com/henvic/httpretty/.gitignore new file mode 100644 index 00000000..9655699f --- /dev/null +++ b/vendor/github.com/henvic/httpretty/.gitignore @@ -0,0 +1,35 @@ +# Docs +doc/*.md +doc/*.1 + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +.cover + +coverage.html +coverage.out + +*.coverprofile diff --git a/vendor/github.com/henvic/httpretty/CONTRIBUTING.md b/vendor/github.com/henvic/httpretty/CONTRIBUTING.md new file mode 100644 index 00000000..8323a006 --- /dev/null +++ b/vendor/github.com/henvic/httpretty/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# Contributing to httpretty +## Bug reports +When reporting bugs, please add information about your operating system and Go version used to compile the code. + +If you can provide a code snippet reproducing the issue, please do so. + +## Code +Please write code that satisfies [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) before submitting a pull-request. +Your code should be properly covered by extensive unit tests. + +## Commit messages +Please follow the Go [commit messages](https://github.com/golang/go/wiki/CommitMessage) convention when contributing code. diff --git a/vendor/github.com/henvic/httpretty/LICENSE.md b/vendor/github.com/henvic/httpretty/LICENSE.md new file mode 100644 index 00000000..426f2a87 --- /dev/null +++ b/vendor/github.com/henvic/httpretty/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Henrique Vicente + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/henvic/httpretty/README.md b/vendor/github.com/henvic/httpretty/README.md new file mode 100644 index 00000000..ae2c674f --- /dev/null +++ b/vendor/github.com/henvic/httpretty/README.md @@ -0,0 +1,100 @@ +# httpretty +[![Go Reference](https://pkg.go.dev/badge/github.com/henvic/httpretty.svg)](https://pkg.go.dev/github.com/henvic/httpretty) [![Build Status](https://github.com/henvic/httpretty/workflows/Tests/badge.svg)](https://github.com/henvic/httpretty/actions?query=workflow%3ATests) [![Coverage Status](https://coveralls.io/repos/henvic/httpretty/badge.svg)](https://coveralls.io/r/henvic/httpretty) [![Go Report Card](https://goreportcard.com/badge/github.com/henvic/httpretty)](https://goreportcard.com/report/github.com/henvic/httpretty) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3669/badge)](https://bestpractices.coreinfrastructure.org/projects/3669) + +Package httpretty prints the HTTP requests of your Go programs pretty on your terminal screen. It is mostly inspired in [curl](https://curl.haxx.se)'s `--verbose` mode, and also on the [httputil.DumpRequest](https://golang.org/pkg/net/http/httputil/) and similar functions. + +[![asciicast](https://asciinema.org/a/297429.svg)](https://asciinema.org/a/297429) + +## Setting up a logger +You can define a logger with something like + +```go +logger := &httpretty.Logger{ + Time: true, + TLS: true, + RequestHeader: true, + RequestBody: true, + ResponseHeader: true, + ResponseBody: true, + Colors: true, // erase line if you don't like colors + Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}}, +} +``` + +This code will set up a logger with sane settings. By default the logger prints nothing but the request line (and the remote address, when using it on the server-side). + +### Using on the client-side +You can set the transport for the [*net/http.Client](https://golang.org/pkg/net/http/#Client) you are using like this: + +```go +client := &http.Client{ + Transport: logger.RoundTripper(http.DefaultTransport), +} + +// from now on, you can use client.Do, client.Get, etc. to create requests. +``` + +If you don't care about setting a new client, you can safely replace your existing http.DefaultClient with this: + +```go +http.DefaultClient.Transport = logger.RoundTripper(http.DefaultClient.Transport) +``` + +Then httpretty is going to print information about regular requests to your terminal when code such as this is called: +```go +if _, err := http.Get("https://www.google.com/"); err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) +} +``` + +However, have in mind you usually want to use a custom *http.Client to control things such as timeout. + +## Logging on the server-side +You can use the logger quickly to log requests on your server. For example: + +```go +logger.Middleware(mux) +``` + +The handler should by a http.Handler. Usually, you want this to be your `http.ServeMux` HTTP entrypoint. + +For working examples, please see the example directory. + +## Filtering +You have two ways to filter a request so it isn't printed by the logger. + +### httpretty.WithHide +You can filter any request by setting a request context before the request reaches `httpretty.RoundTripper`: + +```go +req = req.WithContext(httpretty.WithHide(ctx)) +``` + +### Filter function +A second option is to implement + +```go +type Filter func(req *http.Request) (skip bool, err error) +``` + +and set it as the filter for your logger. For example: + +```go +logger.SetFilter(func filteredURIs(req *http.Request) (bool, error) { + if req.Method != http.MethodGet { + return true, nil + } + + if path := req.URL.Path; path == "/debug" || strings.HasPrefix(path, "/debug/") { + return true, nil + } + + return false +}) +``` + +## Formatters +You can define a formatter for any media type by implementing the Formatter interface. + +We provide a JSONFormatter for convenience (it is not enabled by default). diff --git a/vendor/github.com/henvic/httpretty/httpretty.go b/vendor/github.com/henvic/httpretty/httpretty.go new file mode 100644 index 00000000..3bedebbd --- /dev/null +++ b/vendor/github.com/henvic/httpretty/httpretty.go @@ -0,0 +1,421 @@ +// Package httpretty prints your HTTP requests pretty on your terminal screen. +// You can use this package both on the client-side and on the server-side. +// +// This package provides a better way to view HTTP traffic without httputil +// DumpRequest, DumpRequestOut, and DumpResponse heavy debugging functions. +// +// You can use the logger quickly to log requests you are opening. For example: +// +// package main +// +// import ( +// "fmt" +// "net/http" +// "os" +// +// "github.com/henvic/httpretty" +// ) +// +// func main() { +// logger := &httpretty.Logger{ +// Time: true, +// TLS: true, +// RequestHeader: true, +// RequestBody: true, +// ResponseHeader: true, +// ResponseBody: true, +// Colors: true, +// Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}}, +// } +// +// http.DefaultClient.Transport = logger.RoundTripper(http.DefaultClient.Transport) // tip: you can use it on any *http.Client +// +// if _, err := http.Get("https://www.google.com/"); err != nil { +// fmt.Fprintf(os.Stderr, "%+v\n", err) +// os.Exit(1) +// } +// } +// +// If you pass nil to the logger.RoundTripper it is going to fallback to http.DefaultTransport. +// +// You can use the logger quickly to log requests on your server. For example: +// +// logger := &httpretty.Logger{ +// Time: true, +// TLS: true, +// RequestHeader: true, +// RequestBody: true, +// ResponseHeader: true, +// ResponseBody: true, +// } +// +// logger.Middleware(handler) +// +// Note: server logs don't include response headers set by the server. +// Client logs don't include request headers set by the HTTP client. +package httpretty + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "errors" + "io" + "net/http" + "net/textproto" + "os" + "regexp" + "sync" + + "github.com/henvic/httpretty/internal/color" +) + +// Formatter can be used to format body. +// +// If the Format function returns an error, the content is printed in verbatim after a warning. +// Match receives a media type from the Content-Type field. The body is formatted if it returns true. +type Formatter interface { + Match(mediatype string) bool + Format(w io.Writer, src []byte) error +} + +// WithHide can be used to protect a request from being exposed. +func WithHide(ctx context.Context) context.Context { + return context.WithValue(ctx, contextHide{}, struct{}{}) +} + +// Logger provides a way for you to print client and server-side information about your HTTP traffic. +type Logger struct { + // SkipRequestInfo avoids printing a line showing the request URI on all requests plus a line + // containing the remote address on server-side requests. + SkipRequestInfo bool + + // Time the request began and its duration. + Time bool + + // TLS information, such as certificates and ciphers. + // BUG(henvic): Currently, the TLS information prints after the response header, although it + // should be printed before the request header. + TLS bool + + // RequestHeader set by the client or received from the server. + RequestHeader bool + + // RequestBody sent by the client or received by the server. + RequestBody bool + + // ResponseHeader received by the client or set by the HTTP handlers. + ResponseHeader bool + + // ResponseBody received by the client or set by the server. + ResponseBody bool + + // SkipSanitize bypasses sanitizing headers containing credentials (such as Authorization). + SkipSanitize bool + + // Colors set ANSI escape codes that terminals use to print text in different colors. + Colors bool + + // Align HTTP headers. + Align bool + + // Formatters for the request and response bodies. + // No standard formatters are used. You need to add what you want to use explicitly. + // We provide a JSONFormatter for convenience (add it manually). + Formatters []Formatter + + // MaxRequestBody the logger can print. + // If value is not set and Content-Length is not sent, 4096 bytes is considered. + MaxRequestBody int64 + + // MaxResponseBody the logger can print. + // If value is not set and Content-Length is not sent, 4096 bytes is considered. + MaxResponseBody int64 + + mu sync.Mutex // ensures atomic writes; protects the following fields + w io.Writer + filter Filter + skipHeader map[string]struct{} + bodyFilter BodyFilter + flusher Flusher +} + +// Filter allows you to skip requests. +// +// If an error happens and you want to log it, you can pass a not-null error value. +type Filter func(req *http.Request) (skip bool, err error) + +// BodyFilter allows you to skip printing a HTTP body based on its associated Header. +// +// It can be used for omitting HTTP Request and Response bodies. +// You can filter by checking properties such as Content-Type or Content-Length. +// +// On a HTTP server, this function is called even when no body is present due to +// http.Request always carrying a non-nil value. +type BodyFilter func(h http.Header) (skip bool, err error) + +// Flusher defines how logger prints requests. +type Flusher int + +// Logger can print without flushing, when they are available, or when the request is done. +const ( + // NoBuffer strategy prints anything immediately, without buffering. + // It has the issue of mingling concurrent requests in unpredictable ways. + NoBuffer Flusher = iota + + // OnReady buffers and prints each step of the request or response (header, body) whenever they are ready. + // It reduces mingling caused by mingling but does not give any ordering guarantee, so responses can still be out of order. + OnReady + + // OnEnd buffers the whole request and flushes it once, in the end. + OnEnd +) + +// SetFilter allows you to set a function to skip requests. +// Pass nil to remove the filter. This method is concurrency safe. +func (l *Logger) SetFilter(f Filter) { + l.mu.Lock() + defer l.mu.Unlock() + l.filter = f +} + +// SkipHeader allows you to skip printing specific headers. +// This method is concurrency safe. +func (l *Logger) SkipHeader(headers []string) { + l.mu.Lock() + defer l.mu.Unlock() + m := map[string]struct{}{} + for _, h := range headers { + m[textproto.CanonicalMIMEHeaderKey(h)] = struct{}{} + } + l.skipHeader = m +} + +// SetBodyFilter allows you to set a function to skip printing a body. +// Pass nil to remove the body filter. This method is concurrency safe. +func (l *Logger) SetBodyFilter(f BodyFilter) { + l.mu.Lock() + defer l.mu.Unlock() + l.bodyFilter = f +} + +// SetOutput sets the output destination for the logger. +func (l *Logger) SetOutput(w io.Writer) { + l.mu.Lock() + defer l.mu.Unlock() + l.w = w +} + +// SetFlusher sets the flush strategy for the logger. +func (l *Logger) SetFlusher(f Flusher) { + l.mu.Lock() + defer l.mu.Unlock() + l.flusher = f +} + +func (l *Logger) getWriter() io.Writer { + if l.w == nil { + return os.Stdout + } + + return l.w +} + +func (l *Logger) getFilter() Filter { + l.mu.Lock() + f := l.filter + defer l.mu.Unlock() + return f +} + +func (l *Logger) getBodyFilter() BodyFilter { + l.mu.Lock() + f := l.bodyFilter + defer l.mu.Unlock() + return f +} + +func (l *Logger) cloneSkipHeader() map[string]struct{} { + l.mu.Lock() + skipped := l.skipHeader + l.mu.Unlock() + + m := map[string]struct{}{} + for h := range skipped { + m[h] = struct{}{} + } + + return m +} + +type contextHide struct{} + +type roundTripper struct { + logger *Logger + rt http.RoundTripper +} + +// RoundTripper returns a RoundTripper that uses the logger. +func (l *Logger) RoundTripper(rt http.RoundTripper) http.RoundTripper { + return roundTripper{ + logger: l, + rt: rt, + } +} + +// RoundTrip implements the http.RoundTrip interface. +func (r roundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { + tripper := r.rt + if tripper == nil { + // BUG(henvic): net/http data race condition when the client + // does concurrent requests using the very same HTTP transport. + // See Go standard library issue https://golang.org/issue/30597 + tripper = http.RoundTripper(http.DefaultTransport) + } + l := r.logger + p := newPrinter(l) + defer p.flush() + if hide := req.Context().Value(contextHide{}); hide != nil || p.checkFilter(req) { + return tripper.RoundTrip(req) + } + var tlsClientConfig *tls.Config + if l.Time { + defer p.printTimeRequest()() + } + if !l.SkipRequestInfo { + p.printRequestInfo(req) + } + // Try to get some information from transport + transport, ok := tripper.(*http.Transport) + // If proxy is used, then print information about proxy server + if ok && transport.Proxy != nil { + proxyUrl, err := transport.Proxy(req) + if proxyUrl != nil && err == nil { + p.printf("* Using proxy: %s\n", p.format(color.FgBlue, proxyUrl.String())) + } + } + if ok && transport.TLSClientConfig != nil { + tlsClientConfig = transport.TLSClientConfig + if tlsClientConfig.InsecureSkipVerify { + p.printf("* Skipping TLS verification: %s\n", + p.format(color.FgRed, "connection is susceptible to man-in-the-middle attacks.")) + } + } + // Maybe print outgoing TLS information. + if l.TLS && tlsClientConfig != nil { + // please remember http.Request.TLS is ignored by the HTTP client. + p.printOutgoingClientTLS(tlsClientConfig) + } + p.printRequest(req) + defer func() { + if err != nil { + p.printf("* %s\n", p.format(color.FgRed, err.Error())) + if resp == nil { + return + } + } + if l.TLS { + p.printTLSInfo(resp.TLS, false) + p.printTLSServer(req.Host, resp.TLS) + } + p.printResponse(resp) + }() + return tripper.RoundTrip(req) +} + +// Middleware for logging incoming requests to a HTTP server. +func (l *Logger) Middleware(next http.Handler) http.Handler { + return httpHandler{ + logger: l, + next: next, + } +} + +type httpHandler struct { + logger *Logger + next http.Handler +} + +// ServeHTTP is a middleware for logging incoming requests to a HTTP server. +func (h httpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + l := h.logger + p := newPrinter(l) + defer p.flush() + if hide := req.Context().Value(contextHide{}); hide != nil || p.checkFilter(req) { + h.next.ServeHTTP(w, req) + return + } + if p.logger.Time { + defer p.printTimeRequest()() + } + if !p.logger.SkipRequestInfo { + p.printRequestInfo(req) + } + if p.logger.TLS { + p.printTLSInfo(req.TLS, true) + p.printIncomingClientTLS(req.TLS) + } + p.printRequest(req) + rec := &responseRecorder{ + ResponseWriter: w, + statusCode: http.StatusOK, + maxReadableBody: l.MaxResponseBody, + buf: &bytes.Buffer{}, + } + defer p.printServerResponse(req, rec) + h.next.ServeHTTP(rec, req) +} + +// PrintRequest prints a request, even when WithHide is used to hide it. +// +// It doesn't log TLS connection details or request duration. +func (l *Logger) PrintRequest(req *http.Request) { + var p = printer{logger: l} + if skip := p.checkFilter(req); skip { + return + } + p.printRequest(req) +} + +// PrintResponse prints a response. +func (l *Logger) PrintResponse(resp *http.Response) { + var p = printer{logger: l} + p.printResponse(resp) +} + +// JSONFormatter helps you read unreadable JSON documents. +// +// github.com/tidwall/pretty could be used to add colors to it. +// However, it would add an external dependency. If you want, you can define +// your own formatter using it or anything else. See Formatter. +type JSONFormatter struct{} + +// jsonTypeRE can be used to identify JSON media types, such as +// application/json or application/vnd.api+json. +// +// Source: https://github.com/cli/cli/blob/63a4319f6caedccbadf1bf0317d70b6f0cb1b5b9/internal/authflow/flow.go#L27 +var jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`) + +// Match JSON media type. +func (j *JSONFormatter) Match(mediatype string) bool { + return jsonTypeRE.MatchString(mediatype) +} + +// Format JSON content. +func (j *JSONFormatter) Format(w io.Writer, src []byte) error { + if !json.Valid(src) { + // We want to get the error of json.checkValid, not unmarshal it. + // The happy path has been optimized, maybe prematurely. + if err := json.Unmarshal(src, &json.RawMessage{}); err != nil { + return err + } + } + // avoiding allocation as we use *bytes.Buffer to store the formatted body before printing + dst, ok := w.(*bytes.Buffer) + if !ok { + // mitigating panic to avoid upsetting anyone who uses this directly + return errors.New("underlying writer for JSONFormatter must be *bytes.Buffer") + } + return json.Indent(dst, src, "", " ") +} diff --git a/vendor/github.com/henvic/httpretty/internal/color/color.go b/vendor/github.com/henvic/httpretty/internal/color/color.go new file mode 100644 index 00000000..fb8a6dba --- /dev/null +++ b/vendor/github.com/henvic/httpretty/internal/color/color.go @@ -0,0 +1,164 @@ +// Package color can be used to add color to your terminal using ANSI escape code (or sequences). +// +// See https://en.wikipedia.org/wiki/ANSI_escape_code +// Copy modified from https://github.com/fatih/color +// Copyright 2013 Fatih Arslan +package color + +import ( + "fmt" + "strconv" + "strings" +) + +// Attribute defines a single SGR (Select Graphic Rendition) code. +type Attribute int + +// Base attributes +const ( + Reset Attribute = iota + Bold + Faint + Italic + Underline + BlinkSlow + BlinkRapid + ReverseVideo + Concealed + CrossedOut +) + +// Foreground text colors +const ( + FgBlack Attribute = iota + 30 + FgRed + FgGreen + FgYellow + FgBlue + FgMagenta + FgCyan + FgWhite +) + +// Foreground Hi-Intensity text colors +const ( + FgHiBlack Attribute = iota + 90 + FgHiRed + FgHiGreen + FgHiYellow + FgHiBlue + FgHiMagenta + FgHiCyan + FgHiWhite +) + +// Background text colors +const ( + BgBlack Attribute = iota + 40 + BgRed + BgGreen + BgYellow + BgBlue + BgMagenta + BgCyan + BgWhite +) + +// Background Hi-Intensity text colors +const ( + BgHiBlack Attribute = iota + 100 + BgHiRed + BgHiGreen + BgHiYellow + BgHiBlue + BgHiMagenta + BgHiCyan + BgHiWhite +) + +const ( + escape = "\x1b" + unescape = "\\x1b" +) + +// Format text for terminal. +// You can pass an arbitrary number of Attribute or []Attribute followed by any other values, +// that can either be a string or something else (that is converted to string using fmt.Sprint). +func Format(s ...interface{}) string { + if len(s) == 0 { + return "" + } + + params := []Attribute{} + in := -1 + for i, v := range s { + switch vt := v.(type) { + case []Attribute: + if in == -1 { + params = append(params, vt...) + } else { + s[i] = printExtraColorAttribute(v) + } + case Attribute: + if in == -1 { + params = append(params, vt) + } else { + s[i] = printExtraColorAttribute(v) + } + default: + if in == -1 { + in = i + } + } + } + if in == -1 || len(s[in:]) == 0 { + return "" + } + return wrap(params, fmt.Sprint(s[in:]...)) +} + +func printExtraColorAttribute(v interface{}) string { + return fmt.Sprintf("(EXTRA color.Attribute=%v)", v) +} + +// StripAttributes from input arguments and return unformatted text. +func StripAttributes(s ...interface{}) (raw string) { + in := -1 + for i, v := range s { + switch v.(type) { + case []Attribute, Attribute: + if in != -1 { + s[i] = printExtraColorAttribute(v) + } + default: + if in == -1 { + in = i + } + } + } + if in == -1 { + in = 0 + } + return fmt.Sprint(s[in:]...) +} + +// Escape text for terminal. +func Escape(s string) string { + return strings.Replace(s, escape, unescape, -1) +} + +// sequence returns a formated SGR sequence to be plugged into a "\x1b[...m" +// an example output might be: "1;36" -> bold cyan. +func sequence(params []Attribute) string { + format := make([]string, len(params)) + for i, v := range params { + format[i] = strconv.Itoa(int(v)) + } + + return strings.Join(format, ";") +} + +// wrap the s string with the colors attributes. +func wrap(params []Attribute, s string) string { + return fmt.Sprintf("%s[%sm%s%s[%dm", escape, sequence(params), s, escape, Reset) +} diff --git a/vendor/github.com/henvic/httpretty/internal/header/header.go b/vendor/github.com/henvic/httpretty/internal/header/header.go new file mode 100644 index 00000000..5221ab5d --- /dev/null +++ b/vendor/github.com/henvic/httpretty/internal/header/header.go @@ -0,0 +1,114 @@ +// Package header can be used to sanitize HTTP request and response headers. +package header + +import ( + "fmt" + "net/http" + "strings" +) + +// Sanitize list of headers. +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ can be consulted for header syntax. +func Sanitize(sanitizers map[string]SanitizeHeaderFunc, headers http.Header) http.Header { + var redacted = http.Header{} + + for k, values := range headers { + if s, ok := sanitizers[http.CanonicalHeaderKey(k)]; ok { + redacted[k] = sanitize(s, values) + continue + } + + redacted[k] = values + } + + return redacted +} + +func sanitize(s SanitizeHeaderFunc, values []string) []string { + var redacted = []string{} + + for _, v := range values { + redacted = append(redacted, s(v)) + } + + return redacted +} + +// DefaultSanitizers contains a list of sanitizers to be used for common headers. +var DefaultSanitizers = map[string]SanitizeHeaderFunc{ + "Authorization": AuthorizationSanitizer, + "Set-Cookie": SetCookieSanitizer, + "Cookie": CookieSanitizer, + "Proxy-Authorization": AuthorizationSanitizer, +} + +// SanitizeHeaderFunc implements sanitization for a header value. +type SanitizeHeaderFunc func(string) string + +// AuthorizationSanitizer is used to sanitize Authorization and Proxy-Authorization headers. +func AuthorizationSanitizer(unsafe string) string { + if unsafe == "" { + return "" + } + + directives := strings.SplitN(unsafe, " ", 2) + + l := 0 + + if len(directives) > 1 { + l = len(directives[1]) + } + + if l == 0 { + return directives[0] + } + + return directives[0] + " " + redact(l) +} + +// SetCookieSanitizer is used to sanitize Set-Cookie header. +func SetCookieSanitizer(unsafe string) string { + directives := strings.SplitN(unsafe, ";", 2) + + cookie := strings.SplitN(directives[0], "=", 2) + + l := 0 + + if len(cookie) > 1 { + l = len(cookie[1]) + } + + if len(directives) == 2 { + return fmt.Sprintf("%s=%s; %s", cookie[0], redact(l), strings.TrimPrefix(directives[1], " ")) + } + + return fmt.Sprintf("%s=%s", cookie[0], redact(l)) +} + +// CookieSanitizer is used to sanitize Cookie header. +func CookieSanitizer(unsafe string) string { + cookies := strings.Split(unsafe, ";") + + var list []string + + for _, unsafeCookie := range cookies { + cookie := strings.SplitN(unsafeCookie, "=", 2) + l := 0 + + if len(cookie) > 1 { + l = len(cookie[1]) + } + + list = append(list, fmt.Sprintf("%s=%s", cookie[0], redact(l))) + } + + return strings.Join(list, "; ") +} + +func redact(count int) string { + if count == 0 { + return "" + } + + return "████████████████████" +} diff --git a/vendor/github.com/henvic/httpretty/printer.go b/vendor/github.com/henvic/httpretty/printer.go new file mode 100644 index 00000000..b85b28fb --- /dev/null +++ b/vendor/github.com/henvic/httpretty/printer.go @@ -0,0 +1,602 @@ +package httpretty + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "mime" + "net" + "net/http" + "slices" + "sort" + "strings" + "time" + + "github.com/henvic/httpretty/internal/color" + "github.com/henvic/httpretty/internal/header" +) + +func newPrinter(l *Logger) printer { + l.mu.Lock() + defer l.mu.Unlock() + return printer{ + logger: l, + flusher: l.flusher, + } +} + +type printer struct { + flusher Flusher + logger *Logger + buf bytes.Buffer +} + +func (p *printer) maybeOnReady() { + if p.flusher == OnReady { + p.flush() + } +} + +func (p *printer) flush() { + if p.flusher == NoBuffer { + return + } + p.logger.mu.Lock() + defer p.logger.mu.Unlock() + defer p.buf.Reset() + w := p.logger.getWriter() + fmt.Fprint(w, p.buf.String()) +} + +func (p *printer) print(a ...interface{}) { + p.logger.mu.Lock() + defer p.logger.mu.Unlock() + w := p.logger.getWriter() + if p.flusher == NoBuffer { + fmt.Fprint(w, a...) + return + } + fmt.Fprint(&p.buf, a...) +} + +func (p *printer) println(a ...interface{}) { + p.logger.mu.Lock() + defer p.logger.mu.Unlock() + w := p.logger.getWriter() + if p.flusher == NoBuffer { + fmt.Fprintln(w, a...) + return + } + fmt.Fprintln(&p.buf, a...) +} + +func (p *printer) printf(format string, a ...interface{}) { + p.logger.mu.Lock() + defer p.logger.mu.Unlock() + w := p.logger.getWriter() + if p.flusher == NoBuffer { + fmt.Fprintf(w, format, a...) + return + } + fmt.Fprintf(&p.buf, format, a...) +} + +func (p *printer) printRequest(req *http.Request) { + if p.logger.RequestHeader { + p.printRequestHeader(req) + p.maybeOnReady() + } + if p.logger.RequestBody && req.Body != nil { + p.printRequestBody(req) + p.maybeOnReady() + } +} + +func (p *printer) printRequestInfo(req *http.Request) { + to := req.URL.String() + // req.URL.Host is empty on the request received by a server + if req.URL.Host == "" { + to = req.Host + to + schema := "http://" + if req.TLS != nil { + schema = "https://" + } + to = schema + to + } + p.printf("* Request to %s\n", p.format(color.FgBlue, to)) + if req.RemoteAddr != "" { + p.printf("* Request from %s\n", p.format(color.FgBlue, req.RemoteAddr)) + } +} + +// checkFilter checkes if the request is filtered and if the Request value is nil. +func (p *printer) checkFilter(req *http.Request) (skip bool) { + filter := p.logger.getFilter() + if req == nil { + p.printf("> %s\n", p.format(color.FgRed, "error: null request")) + return true + } + if filter == nil { + return false + } + ok, err := safeFilter(filter, req) + if err != nil { + p.printf("* cannot filter request: %s: %s\n", p.format(color.FgBlue, fmt.Sprintf("%s %s", req.Method, req.URL)), p.format(color.FgRed, err.Error())) + return false // never filter out the request if the filter errored + } + return ok +} + +func safeFilter(filter Filter, req *http.Request) (skip bool, err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("panic: %v", e) + } + }() + return filter(req) +} + +func (p *printer) printResponse(resp *http.Response) { + if resp == nil { + p.printf("< %s\n", p.format(color.FgRed, "error: null response")) + p.maybeOnReady() + return + } + if p.logger.ResponseHeader { + p.printResponseHeader(resp.Proto, resp.Status, resp.Header) + p.maybeOnReady() + } + if p.logger.ResponseBody && resp.Body != nil && (resp.Request == nil || resp.Request.Method != http.MethodHead) { + p.printResponseBodyOut(resp) + p.maybeOnReady() + } +} + +func (p *printer) checkBodyFiltered(h http.Header) (skip bool, err error) { + if f := p.logger.getBodyFilter(); f != nil { + defer func() { + if e := recover(); e != nil { + p.printf("* panic while filtering body: %v\n", e) + } + }() + return f(h) + } + return false, nil +} + +func (p *printer) printResponseBodyOut(resp *http.Response) { + if resp.ContentLength == 0 { + return + } + skip, err := p.checkBodyFiltered(resp.Header) + if err != nil { + p.printf("* %s\n", p.format(color.FgRed, "error on response body filter: ", err.Error())) + } + if skip { + return + } + if contentType := resp.Header.Get("Content-Type"); contentType != "" && isBinaryMediatype(contentType) { + p.println("* body contains binary data") + return + } + if p.logger.MaxResponseBody > 0 && resp.ContentLength > p.logger.MaxResponseBody { + p.printf("* body is too long (%d bytes) to print, skipping (longer than %d bytes)\n", resp.ContentLength, p.logger.MaxResponseBody) + return + } + contentType := resp.Header.Get("Content-Type") + if resp.ContentLength == -1 { + if newBody := p.printBodyUnknownLength(contentType, p.logger.MaxResponseBody, resp.Body); newBody != nil { + resp.Body = newBody + } + return + } + var buf bytes.Buffer + tee := io.TeeReader(resp.Body, &buf) + defer resp.Body.Close() + defer func() { + resp.Body = io.NopCloser(&buf) + }() + p.printBodyReader(contentType, tee) +} + +// isBinary uses heuristics to guess if file is binary (actually, "printable" in the terminal). +// See discussion at https://groups.google.com/forum/#!topic/golang-nuts/YeLL7L7SwWs +func isBinary(body []byte) bool { + if len(body) > 512 { + body = body[512:] + } + // If file contains UTF-8 OR UTF-16 BOM, consider it non-binary. + // Reference: https://tools.ietf.org/html/draft-ietf-websec-mime-sniff-03#section-5 + if len(body) >= 3 && (bytes.Equal(body[:2], []byte{0xFE, 0xFF}) || // UTF-16BE BOM + bytes.Equal(body[:2], []byte{0xFF, 0xFE}) || // UTF-16LE BOM + bytes.Equal(body[:3], []byte{0xEF, 0xBB, 0xBF})) { // UTF-8 BOM + return false + } + // If all of the first n octets are binary data octets, consider it binary. + // Reference: https://github.com/golang/go/blob/349e7df2c3d0f9b5429e7c86121499c137faac7e/src/net/http/sniff.go#L297-L309 + // c.f. section 5, step 4. + for _, b := range body { + switch { + case b <= 0x08, + b == 0x0B, + 0x0E <= b && b <= 0x1A, + 0x1C <= b && b <= 0x1F: + return true + } + } + // Otherwise, check against a white list of binary mimetypes. + mediatype, _, err := mime.ParseMediaType(http.DetectContentType(body)) + if err != nil { + return false + } + return isBinaryMediatype(mediatype) +} + +var binaryMediatypes = map[string]struct{}{ + "application/pdf": {}, + "application/postscript": {}, + "image": {}, // for practical reasons, any image (including SVG) is considered binary data + "audio": {}, + "application/ogg": {}, + "video": {}, + "application/vnd.ms-fontobject": {}, + "font": {}, + "application/x-gzip": {}, + "application/zip": {}, + "application/x-rar-compressed": {}, + "application/wasm": {}, +} + +func isBinaryMediatype(mediatype string) bool { + if _, ok := binaryMediatypes[mediatype]; ok { + return true + } + if parts := strings.SplitN(mediatype, "/", 2); len(parts) == 2 { + if _, ok := binaryMediatypes[parts[0]]; ok { + return true + } + } + return false +} + +const maxDefaultUnknownReadable = 4096 // bytes + +func (p *printer) printBodyUnknownLength(contentType string, maxLength int64, r io.ReadCloser) (newBody io.ReadCloser) { + if maxLength == 0 { + maxLength = maxDefaultUnknownReadable + } + pb := make([]byte, maxLength+1) // read one extra bit to assure the length is longer than acceptable + n, err := io.ReadFull(r, pb) + pb = pb[0:n] // trim any nil symbols left after writing in the byte slice. + buf := bytes.NewReader(pb) + newBody = newBodyReaderBuf(buf, r) + switch { + // Server requests always return req.Body != nil, but the Reader returns io.EOF immediately. + // Avoiding returning early to mitigate any risk of bad reader implementations that might + // send something even after returning io.EOF if read again. + case err == io.EOF && n == 0: + case err == nil && int64(n) > maxLength: + p.printf("* body is too long, skipping (contains more than %d bytes)\n", n-1) + case err == io.ErrUnexpectedEOF || err == nil: + // cannot pass same bytes reader below because we only read it once. + p.printBodyReader(contentType, bytes.NewReader(pb)) + default: + p.printf("* cannot read body: %v (%d bytes read)\n", err, n) + } + return +} + +func findPeerCertificate(hostname string, state *tls.ConnectionState) (cert *x509.Certificate) { + if chains := state.VerifiedChains; chains != nil && chains[0] != nil && chains[0][0] != nil { + return chains[0][0] + } + if hostname == "" && len(state.PeerCertificates) > 0 { + // skip finding a match for a given hostname if hostname is not available (e.g., a client certificate) + return state.PeerCertificates[0] + } + // the chain is not created when tls.Config.InsecureSkipVerify is set, then let's try to find a match to display + for _, cert := range state.PeerCertificates { + if err := cert.VerifyHostname(hostname); err == nil { + return cert + } + } + return nil +} + +func (p *printer) printTLSInfo(state *tls.ConnectionState, skipVerifyChains bool) { + if state == nil { + return + } + protocol := tlsProtocolVersions[state.Version] + if protocol == "" { + protocol = fmt.Sprintf("%#v", state.Version) + } + cipher := tlsCiphers[state.CipherSuite] + if cipher == "" { + cipher = fmt.Sprintf("%#v", state.CipherSuite) + } + p.printf("* TLS connection using %s / %s", p.format(color.FgBlue, protocol), p.format(color.FgBlue, cipher)) + if !skipVerifyChains && state.VerifiedChains == nil { + p.print(" (insecure=true)") + } + p.println() + if state.NegotiatedProtocol != "" { + p.printf("* ALPN: %v accepted\n", p.format(color.FgBlue, state.NegotiatedProtocol)) + } +} + +func (p *printer) printOutgoingClientTLS(config *tls.Config) { + if config == nil || len(config.Certificates) == 0 { + return + } + p.println("* Client certificate:") + // Please notice tls.Config.BuildNameToCertificate() doesn't store the certificate Leaf field. + // You need to explicitly parse and store it with something such as: + // cert.Leaf, err = x509.ParseCertificate(cert.Certificate) + if cert := config.Certificates[0].Leaf; cert != nil { + p.printCertificate("", cert) + } else { + p.println(`** unparsed certificate found, skipping`) + } +} + +func (p *printer) printIncomingClientTLS(state *tls.ConnectionState) { + // if no TLS state is null or no client TLS certificate is found, return early. + if state == nil || len(state.PeerCertificates) == 0 { + return + } + p.println("* Client certificate:") + if cert := findPeerCertificate("", state); cert != nil { + p.printCertificate("", cert) + } else { + p.println(p.format(color.FgRed, "** No valid certificate was found")) + } +} + +func (p *printer) printTLSServer(host string, state *tls.ConnectionState) { + if state == nil { + return + } + hostname, _, err := net.SplitHostPort(host) + if err != nil { + // assume the error is due to "missing port in address" + hostname = host + } + p.println("* Server certificate:") + if cert := findPeerCertificate(hostname, state); cert != nil { + // server certificate messages are slightly similar to how "curl -v" shows + p.printCertificate(hostname, cert) + } else { + p.println(p.format(color.FgRed, "** No valid certificate was found")) + } +} + +func (p *printer) printCertificate(hostname string, cert *x509.Certificate) { + p.printf(`* subject: %v +* start date: %v +* expire date: %v +* issuer: %v +`, + p.format(color.FgBlue, cert.Subject), + p.format(color.FgBlue, cert.NotBefore.Format(time.UnixDate)), + p.format(color.FgBlue, cert.NotAfter.Format(time.UnixDate)), + p.format(color.FgBlue, cert.Issuer), + ) + if hostname == "" { + return + } + if err := cert.VerifyHostname(hostname); err != nil { + p.printf("* %s\n", p.format(color.FgRed, err.Error())) + return + } + p.println("* TLS certificate verify ok.") +} + +func (p *printer) printServerResponse(req *http.Request, rec *responseRecorder) { + if p.logger.ResponseHeader { + // TODO(henvic): see how httptest.ResponseRecorder adds extra headers due to Content-Type detection + // and other stuff (Date). It would be interesting to show them here too (either as default or opt-in). + p.printResponseHeader(req.Proto, fmt.Sprintf("%d %s", rec.statusCode, http.StatusText(rec.statusCode)), rec.Header()) + } + if !p.logger.ResponseBody || rec.size == 0 { + return + } + skip, err := p.checkBodyFiltered(rec.Header()) + if err != nil { + p.printf("* %s\n", p.format(color.FgRed, "error on response body filter: ", err.Error())) + } + if skip { + return + } + if mediatype := req.Header.Get("Content-Type"); mediatype != "" && isBinaryMediatype(mediatype) { + p.println("* body contains binary data") + return + } + if p.logger.MaxResponseBody > 0 && rec.size > p.logger.MaxResponseBody { + p.printf("* body is too long (%d bytes) to print, skipping (longer than %d bytes)\n", rec.size, p.logger.MaxResponseBody) + return + } + p.printBodyReader(rec.Header().Get("Content-Type"), rec.buf) +} + +func (p *printer) printResponseHeader(proto, status string, h http.Header) { + p.printf("< %s %s\n", + p.format(color.FgBlue, color.Bold, proto), + p.format(color.FgRed, status)) + p.printHeaders('<', h) + p.println() +} + +func (p *printer) printBodyReader(contentType string, r io.Reader) { + mediatype, _, _ := mime.ParseMediaType(contentType) + body, err := io.ReadAll(r) + if err != nil { + p.printf("* cannot read body: %v\n", p.format(color.FgRed, err.Error())) + return + } + if isBinary(body) { + p.println("* body contains binary data") + return + } + for _, f := range p.logger.Formatters { + if ok := p.safeBodyMatch(f, mediatype); !ok { + continue + } + var formatted bytes.Buffer + switch err := p.safeBodyFormat(f, &formatted, body); { + case err != nil: + p.printf("* body cannot be formatted: %v\n%s\n", p.format(color.FgRed, err.Error()), string(body)) + default: + p.println(formatted.String()) + } + return + } + + p.println(string(body)) +} + +func (p *printer) safeBodyMatch(f Formatter, mediatype string) bool { + defer func() { + if e := recover(); e != nil { + p.printf("* panic while testing body format: %v\n", e) + } + }() + return f.Match(mediatype) +} + +func (p *printer) safeBodyFormat(f Formatter, w io.Writer, src []byte) (err error) { + defer func() { + // should not return panic as error because we want to try the next formatter + if e := recover(); e != nil { + err = fmt.Errorf("panic: %v", e) + } + }() + return f.Format(w, src) +} + +func (p *printer) format(s ...interface{}) string { + if p.logger.Colors { + return color.Format(s...) + } + return color.StripAttributes(s...) +} + +func (p *printer) printHeaders(prefix rune, h http.Header) { + if !p.logger.SkipSanitize { + h = header.Sanitize(header.DefaultSanitizers, h) + } + + longest, sorted := sortHeaderKeys(h, p.logger.cloneSkipHeader()) + for _, key := range sorted { + for _, v := range h[key] { + var pad string + if p.logger.Align { + pad = strings.Repeat(" ", longest-len(key)) + } + p.printf("%c %s%s %s%s\n", prefix, + p.format(color.FgBlue, color.Bold, key), + p.format(color.FgRed, ":"), + pad, + p.format(color.FgYellow, v)) + } + } +} + +func sortHeaderKeys(h http.Header, skipped map[string]struct{}) (int, []string) { + var ( + keys = make([]string, 0, len(h)) + longest int + ) + for key := range h { + if _, skip := skipped[key]; skip { + continue + } + keys = append(keys, key) + if l := len(key); l > longest { + longest = l + } + } + sort.Strings(keys) + if i := slices.Index(keys, "Host"); i > -1 { + keys = append([]string{"Host"}, slices.Delete(keys, i, i+1)...) + } + return longest, keys +} + +func (p *printer) printRequestHeader(req *http.Request) { + p.printf("> %s %s %s\n", + p.format(color.FgBlue, color.Bold, req.Method), + p.format(color.FgYellow, req.URL.RequestURI()), + p.format(color.FgBlue, req.Proto)) + p.printHeaders('>', addRequestHeaders(req)) + p.println() +} + +// addRequestHeaders returns a copy of the given header with an additional headers set, if known. +func addRequestHeaders(req *http.Request) http.Header { + cp := http.Header{} + for k, v := range req.Header { + cp[k] = v + } + + if len(req.Header.Values("Content-Length")) == 0 && req.ContentLength > 0 { + cp.Set("Content-Length", fmt.Sprintf("%d", req.ContentLength)) + } + + host := req.Host + if host == "" { + host = req.URL.Host + } + if host != "" { + cp.Set("Host", host) + } + return cp +} + +func (p *printer) printRequestBody(req *http.Request) { + // For client requests, a request with zero content-length and no body is also treated as unknown. + if req.Body == nil { + return + } + skip, err := p.checkBodyFiltered(req.Header) + if err != nil { + p.printf("* %s\n", p.format(color.FgRed, "error on request body filter: ", err.Error())) + } + if skip { + return + } + if mediatype := req.Header.Get("Content-Type"); mediatype != "" && isBinaryMediatype(mediatype) { + p.println("* body contains binary data") + return + } + // TODO(henvic): add support for printing multipart/formdata information as body (to responses too). + if p.logger.MaxRequestBody > 0 && req.ContentLength > p.logger.MaxRequestBody { + p.printf("* body is too long (%d bytes) to print, skipping (longer than %d bytes)\n", + req.ContentLength, p.logger.MaxRequestBody) + return + } + contentType := req.Header.Get("Content-Type") + if req.ContentLength > 0 { + var buf bytes.Buffer + tee := io.TeeReader(req.Body, &buf) + defer req.Body.Close() + defer func() { + req.Body = io.NopCloser(&buf) + }() + p.printBodyReader(contentType, tee) + return + } + if newBody := p.printBodyUnknownLength(contentType, p.logger.MaxRequestBody, req.Body); newBody != nil { + req.Body = newBody + } +} + +func (p *printer) printTimeRequest() (end func()) { + startRequest := time.Now() + p.printf("* Request at %v\n", startRequest) + return func() { + p.printf("* Request took %v\n", time.Since(startRequest)) + } +} diff --git a/vendor/github.com/henvic/httpretty/recorder.go b/vendor/github.com/henvic/httpretty/recorder.go new file mode 100644 index 00000000..9b55a655 --- /dev/null +++ b/vendor/github.com/henvic/httpretty/recorder.go @@ -0,0 +1,53 @@ +package httpretty + +import ( + "bytes" + "io" + "net/http" +) + +type bodyCloser struct { + r io.Reader + close func() error +} + +func (bc *bodyCloser) Read(p []byte) (n int, err error) { + return bc.r.Read(p) +} + +func (bc *bodyCloser) Close() error { + return bc.close() +} + +func newBodyReaderBuf(buf io.Reader, body io.ReadCloser) *bodyCloser { + return &bodyCloser{ + r: io.MultiReader(buf, body), + close: body.Close, + } +} + +type responseRecorder struct { + http.ResponseWriter + statusCode int + maxReadableBody int64 + size int64 + buf *bytes.Buffer +} + +// Write the data to the connection as part of an HTTP reply, and records it. +func (rr *responseRecorder) Write(p []byte) (int, error) { + rr.size += int64(len(p)) + if rr.maxReadableBody > 0 && rr.size > rr.maxReadableBody { + rr.buf = nil + return rr.ResponseWriter.Write(p) + } + defer rr.buf.Write(p) + return rr.ResponseWriter.Write(p) +} + +// WriteHeader sends an HTTP response header with the provided +// status code, and records it. +func (rr *responseRecorder) WriteHeader(statusCode int) { + rr.ResponseWriter.WriteHeader(statusCode) + rr.statusCode = statusCode +} diff --git a/vendor/github.com/henvic/httpretty/tls.go b/vendor/github.com/henvic/httpretty/tls.go new file mode 100644 index 00000000..70c09d18 --- /dev/null +++ b/vendor/github.com/henvic/httpretty/tls.go @@ -0,0 +1,49 @@ +package httpretty + +// A list of cipher suite IDs that are, or have been, implemented by the +// crypto/tls package. +// See https://www.iana.org/assignments/tls-parameters/tls-parameters.xml +// See https://github.com/golang/go/blob/c2edcf4b1253fdebc13df8a25979904c3ef01c66/src/crypto/tls/cipher_suites.go +var tlsCiphers = map[uint16]string{ + // TLS 1.0 - 1.2 cipher suites. + 0x0005: "TLS_RSA_WITH_RC4_128_SHA", + 0x000a: "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + 0x002f: "TLS_RSA_WITH_AES_128_CBC_SHA", + 0x0035: "TLS_RSA_WITH_AES_256_CBC_SHA", + 0x003c: "TLS_RSA_WITH_AES_128_CBC_SHA256", + 0x009c: "TLS_RSA_WITH_AES_128_GCM_SHA256", + 0x009d: "TLS_RSA_WITH_AES_256_GCM_SHA384", + 0xc007: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + 0xc009: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + 0xc00a: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + 0xc011: "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + 0xc012: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + 0xc013: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + 0xc014: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + 0xc023: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + 0xc027: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + 0xc02f: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + 0xc02b: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + 0xc030: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + 0xc02c: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + 0xcca8: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + 0xcca9: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + + // TLS 1.3 cipher suites. + 0x1301: "TLS_AES_128_GCM_SHA256", + 0x1302: "TLS_AES_256_GCM_SHA384", + 0x1303: "TLS_CHACHA20_POLY1305_SHA256", + + // TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator + // that the client is doing version fallback. See RFC 7507. + 0x5600: "TLS_FALLBACK_SCSV", +} + +// List of TLS protocol versions supported by Go. +// See https://github.com/golang/go/blob/f4a8bf128364e852cff87cf404a5c16c457ef8f6/src/crypto/tls/common.go +var tlsProtocolVersions = map[uint16]string{ + 0x0301: "TLS 1.0", + 0x0302: "TLS 1.1", + 0x0303: "TLS 1.2", + 0x0304: "TLS 1.3", +} diff --git a/vendor/github.com/json-iterator/go/.codecov.yml b/vendor/github.com/json-iterator/go/.codecov.yml new file mode 100644 index 00000000..955dc0be --- /dev/null +++ b/vendor/github.com/json-iterator/go/.codecov.yml @@ -0,0 +1,3 @@ +ignore: + - "output_tests/.*" + diff --git a/vendor/github.com/json-iterator/go/.gitignore b/vendor/github.com/json-iterator/go/.gitignore new file mode 100644 index 00000000..15556530 --- /dev/null +++ b/vendor/github.com/json-iterator/go/.gitignore @@ -0,0 +1,4 @@ +/vendor +/bug_test.go +/coverage.txt +/.idea diff --git a/vendor/github.com/json-iterator/go/.travis.yml b/vendor/github.com/json-iterator/go/.travis.yml new file mode 100644 index 00000000..449e67cd --- /dev/null +++ b/vendor/github.com/json-iterator/go/.travis.yml @@ -0,0 +1,14 @@ +language: go + +go: + - 1.8.x + - 1.x + +before_install: + - go get -t -v ./... + +script: + - ./test.sh + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/json-iterator/go/Gopkg.lock b/vendor/github.com/json-iterator/go/Gopkg.lock new file mode 100644 index 00000000..c8a9fbb3 --- /dev/null +++ b/vendor/github.com/json-iterator/go/Gopkg.lock @@ -0,0 +1,21 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/modern-go/concurrent" + packages = ["."] + revision = "e0a39a4cb4216ea8db28e22a69f4ec25610d513a" + version = "1.0.0" + +[[projects]] + name = "github.com/modern-go/reflect2" + packages = ["."] + revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" + version = "1.0.1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "ea54a775e5a354cb015502d2e7aa4b74230fc77e894f34a838b268c25ec8eeb8" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/json-iterator/go/Gopkg.toml b/vendor/github.com/json-iterator/go/Gopkg.toml new file mode 100644 index 00000000..313a0f88 --- /dev/null +++ b/vendor/github.com/json-iterator/go/Gopkg.toml @@ -0,0 +1,26 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + +ignored = ["github.com/davecgh/go-spew*","github.com/google/gofuzz*","github.com/stretchr/testify*"] + +[[constraint]] + name = "github.com/modern-go/reflect2" + version = "1.0.1" diff --git a/vendor/github.com/json-iterator/go/LICENSE b/vendor/github.com/json-iterator/go/LICENSE new file mode 100644 index 00000000..2cf4f5ab --- /dev/null +++ b/vendor/github.com/json-iterator/go/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 json-iterator + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/json-iterator/go/README.md b/vendor/github.com/json-iterator/go/README.md new file mode 100644 index 00000000..c589addf --- /dev/null +++ b/vendor/github.com/json-iterator/go/README.md @@ -0,0 +1,85 @@ +[![Sourcegraph](https://sourcegraph.com/github.com/json-iterator/go/-/badge.svg)](https://sourcegraph.com/github.com/json-iterator/go?badge) +[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/json-iterator/go) +[![Build Status](https://travis-ci.org/json-iterator/go.svg?branch=master)](https://travis-ci.org/json-iterator/go) +[![codecov](https://codecov.io/gh/json-iterator/go/branch/master/graph/badge.svg)](https://codecov.io/gh/json-iterator/go) +[![rcard](https://goreportcard.com/badge/github.com/json-iterator/go)](https://goreportcard.com/report/github.com/json-iterator/go) +[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/json-iterator/go/master/LICENSE) +[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/json-iterator/Lobby) + +A high-performance 100% compatible drop-in replacement of "encoding/json" + +# Benchmark + +![benchmark](http://jsoniter.com/benchmarks/go-benchmark.png) + +Source code: https://github.com/json-iterator/go-benchmark/blob/master/src/github.com/json-iterator/go-benchmark/benchmark_medium_payload_test.go + +Raw Result (easyjson requires static code generation) + +| | ns/op | allocation bytes | allocation times | +| --------------- | ----------- | ---------------- | ---------------- | +| std decode | 35510 ns/op | 1960 B/op | 99 allocs/op | +| easyjson decode | 8499 ns/op | 160 B/op | 4 allocs/op | +| jsoniter decode | 5623 ns/op | 160 B/op | 3 allocs/op | +| std encode | 2213 ns/op | 712 B/op | 5 allocs/op | +| easyjson encode | 883 ns/op | 576 B/op | 3 allocs/op | +| jsoniter encode | 837 ns/op | 384 B/op | 4 allocs/op | + +Always benchmark with your own workload. +The result depends heavily on the data input. + +# Usage + +100% compatibility with standard lib + +Replace + +```go +import "encoding/json" +json.Marshal(&data) +``` + +with + +```go +import jsoniter "github.com/json-iterator/go" + +var json = jsoniter.ConfigCompatibleWithStandardLibrary +json.Marshal(&data) +``` + +Replace + +```go +import "encoding/json" +json.Unmarshal(input, &data) +``` + +with + +```go +import jsoniter "github.com/json-iterator/go" + +var json = jsoniter.ConfigCompatibleWithStandardLibrary +json.Unmarshal(input, &data) +``` + +[More documentation](http://jsoniter.com/migrate-from-go-std.html) + +# How to get + +``` +go get github.com/json-iterator/go +``` + +# Contribution Welcomed ! + +Contributors + +- [thockin](https://github.com/thockin) +- [mattn](https://github.com/mattn) +- [cch123](https://github.com/cch123) +- [Oleg Shaldybin](https://github.com/olegshaldybin) +- [Jason Toffaletti](https://github.com/toffaletti) + +Report issue or pull request, or email taowen@gmail.com, or [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/json-iterator/Lobby) diff --git a/vendor/github.com/json-iterator/go/adapter.go b/vendor/github.com/json-iterator/go/adapter.go new file mode 100644 index 00000000..92d2cc4a --- /dev/null +++ b/vendor/github.com/json-iterator/go/adapter.go @@ -0,0 +1,150 @@ +package jsoniter + +import ( + "bytes" + "io" +) + +// RawMessage to make replace json with jsoniter +type RawMessage []byte + +// Unmarshal adapts to json/encoding Unmarshal API +// +// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. +// Refer to https://godoc.org/encoding/json#Unmarshal for more information +func Unmarshal(data []byte, v interface{}) error { + return ConfigDefault.Unmarshal(data, v) +} + +// UnmarshalFromString is a convenient method to read from string instead of []byte +func UnmarshalFromString(str string, v interface{}) error { + return ConfigDefault.UnmarshalFromString(str, v) +} + +// Get quick method to get value from deeply nested JSON structure +func Get(data []byte, path ...interface{}) Any { + return ConfigDefault.Get(data, path...) +} + +// Marshal adapts to json/encoding Marshal API +// +// Marshal returns the JSON encoding of v, adapts to json/encoding Marshal API +// Refer to https://godoc.org/encoding/json#Marshal for more information +func Marshal(v interface{}) ([]byte, error) { + return ConfigDefault.Marshal(v) +} + +// MarshalIndent same as json.MarshalIndent. Prefix is not supported. +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { + return ConfigDefault.MarshalIndent(v, prefix, indent) +} + +// MarshalToString convenient method to write as string instead of []byte +func MarshalToString(v interface{}) (string, error) { + return ConfigDefault.MarshalToString(v) +} + +// NewDecoder adapts to json/stream NewDecoder API. +// +// NewDecoder returns a new decoder that reads from r. +// +// Instead of a json/encoding Decoder, an Decoder is returned +// Refer to https://godoc.org/encoding/json#NewDecoder for more information +func NewDecoder(reader io.Reader) *Decoder { + return ConfigDefault.NewDecoder(reader) +} + +// Decoder reads and decodes JSON values from an input stream. +// Decoder provides identical APIs with json/stream Decoder (Token() and UseNumber() are in progress) +type Decoder struct { + iter *Iterator +} + +// Decode decode JSON into interface{} +func (adapter *Decoder) Decode(obj interface{}) error { + if adapter.iter.head == adapter.iter.tail && adapter.iter.reader != nil { + if !adapter.iter.loadMore() { + return io.EOF + } + } + adapter.iter.ReadVal(obj) + err := adapter.iter.Error + if err == io.EOF { + return nil + } + return adapter.iter.Error +} + +// More is there more? +func (adapter *Decoder) More() bool { + iter := adapter.iter + if iter.Error != nil { + return false + } + c := iter.nextToken() + if c == 0 { + return false + } + iter.unreadByte() + return c != ']' && c != '}' +} + +// Buffered remaining buffer +func (adapter *Decoder) Buffered() io.Reader { + remaining := adapter.iter.buf[adapter.iter.head:adapter.iter.tail] + return bytes.NewReader(remaining) +} + +// UseNumber causes the Decoder to unmarshal a number into an interface{} as a +// Number instead of as a float64. +func (adapter *Decoder) UseNumber() { + cfg := adapter.iter.cfg.configBeforeFrozen + cfg.UseNumber = true + adapter.iter.cfg = cfg.frozeWithCacheReuse(adapter.iter.cfg.extraExtensions) +} + +// DisallowUnknownFields causes the Decoder to return an error when the destination +// is a struct and the input contains object keys which do not match any +// non-ignored, exported fields in the destination. +func (adapter *Decoder) DisallowUnknownFields() { + cfg := adapter.iter.cfg.configBeforeFrozen + cfg.DisallowUnknownFields = true + adapter.iter.cfg = cfg.frozeWithCacheReuse(adapter.iter.cfg.extraExtensions) +} + +// NewEncoder same as json.NewEncoder +func NewEncoder(writer io.Writer) *Encoder { + return ConfigDefault.NewEncoder(writer) +} + +// Encoder same as json.Encoder +type Encoder struct { + stream *Stream +} + +// Encode encode interface{} as JSON to io.Writer +func (adapter *Encoder) Encode(val interface{}) error { + adapter.stream.WriteVal(val) + adapter.stream.WriteRaw("\n") + adapter.stream.Flush() + return adapter.stream.Error +} + +// SetIndent set the indention. Prefix is not supported +func (adapter *Encoder) SetIndent(prefix, indent string) { + config := adapter.stream.cfg.configBeforeFrozen + config.IndentionStep = len(indent) + adapter.stream.cfg = config.frozeWithCacheReuse(adapter.stream.cfg.extraExtensions) +} + +// SetEscapeHTML escape html by default, set to false to disable +func (adapter *Encoder) SetEscapeHTML(escapeHTML bool) { + config := adapter.stream.cfg.configBeforeFrozen + config.EscapeHTML = escapeHTML + adapter.stream.cfg = config.frozeWithCacheReuse(adapter.stream.cfg.extraExtensions) +} + +// Valid reports whether data is a valid JSON encoding. +func Valid(data []byte) bool { + return ConfigDefault.Valid(data) +} diff --git a/vendor/github.com/json-iterator/go/any.go b/vendor/github.com/json-iterator/go/any.go new file mode 100644 index 00000000..f6b8aeab --- /dev/null +++ b/vendor/github.com/json-iterator/go/any.go @@ -0,0 +1,325 @@ +package jsoniter + +import ( + "errors" + "fmt" + "github.com/modern-go/reflect2" + "io" + "reflect" + "strconv" + "unsafe" +) + +// Any generic object representation. +// The lazy json implementation holds []byte and parse lazily. +type Any interface { + LastError() error + ValueType() ValueType + MustBeValid() Any + ToBool() bool + ToInt() int + ToInt32() int32 + ToInt64() int64 + ToUint() uint + ToUint32() uint32 + ToUint64() uint64 + ToFloat32() float32 + ToFloat64() float64 + ToString() string + ToVal(val interface{}) + Get(path ...interface{}) Any + Size() int + Keys() []string + GetInterface() interface{} + WriteTo(stream *Stream) +} + +type baseAny struct{} + +func (any *baseAny) Get(path ...interface{}) Any { + return &invalidAny{baseAny{}, fmt.Errorf("GetIndex %v from simple value", path)} +} + +func (any *baseAny) Size() int { + return 0 +} + +func (any *baseAny) Keys() []string { + return []string{} +} + +func (any *baseAny) ToVal(obj interface{}) { + panic("not implemented") +} + +// WrapInt32 turn int32 into Any interface +func WrapInt32(val int32) Any { + return &int32Any{baseAny{}, val} +} + +// WrapInt64 turn int64 into Any interface +func WrapInt64(val int64) Any { + return &int64Any{baseAny{}, val} +} + +// WrapUint32 turn uint32 into Any interface +func WrapUint32(val uint32) Any { + return &uint32Any{baseAny{}, val} +} + +// WrapUint64 turn uint64 into Any interface +func WrapUint64(val uint64) Any { + return &uint64Any{baseAny{}, val} +} + +// WrapFloat64 turn float64 into Any interface +func WrapFloat64(val float64) Any { + return &floatAny{baseAny{}, val} +} + +// WrapString turn string into Any interface +func WrapString(val string) Any { + return &stringAny{baseAny{}, val} +} + +// Wrap turn a go object into Any interface +func Wrap(val interface{}) Any { + if val == nil { + return &nilAny{} + } + asAny, isAny := val.(Any) + if isAny { + return asAny + } + typ := reflect2.TypeOf(val) + switch typ.Kind() { + case reflect.Slice: + return wrapArray(val) + case reflect.Struct: + return wrapStruct(val) + case reflect.Map: + return wrapMap(val) + case reflect.String: + return WrapString(val.(string)) + case reflect.Int: + if strconv.IntSize == 32 { + return WrapInt32(int32(val.(int))) + } + return WrapInt64(int64(val.(int))) + case reflect.Int8: + return WrapInt32(int32(val.(int8))) + case reflect.Int16: + return WrapInt32(int32(val.(int16))) + case reflect.Int32: + return WrapInt32(val.(int32)) + case reflect.Int64: + return WrapInt64(val.(int64)) + case reflect.Uint: + if strconv.IntSize == 32 { + return WrapUint32(uint32(val.(uint))) + } + return WrapUint64(uint64(val.(uint))) + case reflect.Uintptr: + if ptrSize == 32 { + return WrapUint32(uint32(val.(uintptr))) + } + return WrapUint64(uint64(val.(uintptr))) + case reflect.Uint8: + return WrapUint32(uint32(val.(uint8))) + case reflect.Uint16: + return WrapUint32(uint32(val.(uint16))) + case reflect.Uint32: + return WrapUint32(uint32(val.(uint32))) + case reflect.Uint64: + return WrapUint64(val.(uint64)) + case reflect.Float32: + return WrapFloat64(float64(val.(float32))) + case reflect.Float64: + return WrapFloat64(val.(float64)) + case reflect.Bool: + if val.(bool) == true { + return &trueAny{} + } + return &falseAny{} + } + return &invalidAny{baseAny{}, fmt.Errorf("unsupported type: %v", typ)} +} + +// ReadAny read next JSON element as an Any object. It is a better json.RawMessage. +func (iter *Iterator) ReadAny() Any { + return iter.readAny() +} + +func (iter *Iterator) readAny() Any { + c := iter.nextToken() + switch c { + case '"': + iter.unreadByte() + return &stringAny{baseAny{}, iter.ReadString()} + case 'n': + iter.skipThreeBytes('u', 'l', 'l') // null + return &nilAny{} + case 't': + iter.skipThreeBytes('r', 'u', 'e') // true + return &trueAny{} + case 'f': + iter.skipFourBytes('a', 'l', 's', 'e') // false + return &falseAny{} + case '{': + return iter.readObjectAny() + case '[': + return iter.readArrayAny() + case '-': + return iter.readNumberAny(false) + case 0: + return &invalidAny{baseAny{}, errors.New("input is empty")} + default: + return iter.readNumberAny(true) + } +} + +func (iter *Iterator) readNumberAny(positive bool) Any { + iter.startCapture(iter.head - 1) + iter.skipNumber() + lazyBuf := iter.stopCapture() + return &numberLazyAny{baseAny{}, iter.cfg, lazyBuf, nil} +} + +func (iter *Iterator) readObjectAny() Any { + iter.startCapture(iter.head - 1) + iter.skipObject() + lazyBuf := iter.stopCapture() + return &objectLazyAny{baseAny{}, iter.cfg, lazyBuf, nil} +} + +func (iter *Iterator) readArrayAny() Any { + iter.startCapture(iter.head - 1) + iter.skipArray() + lazyBuf := iter.stopCapture() + return &arrayLazyAny{baseAny{}, iter.cfg, lazyBuf, nil} +} + +func locateObjectField(iter *Iterator, target string) []byte { + var found []byte + iter.ReadObjectCB(func(iter *Iterator, field string) bool { + if field == target { + found = iter.SkipAndReturnBytes() + return false + } + iter.Skip() + return true + }) + return found +} + +func locateArrayElement(iter *Iterator, target int) []byte { + var found []byte + n := 0 + iter.ReadArrayCB(func(iter *Iterator) bool { + if n == target { + found = iter.SkipAndReturnBytes() + return false + } + iter.Skip() + n++ + return true + }) + return found +} + +func locatePath(iter *Iterator, path []interface{}) Any { + for i, pathKeyObj := range path { + switch pathKey := pathKeyObj.(type) { + case string: + valueBytes := locateObjectField(iter, pathKey) + if valueBytes == nil { + return newInvalidAny(path[i:]) + } + iter.ResetBytes(valueBytes) + case int: + valueBytes := locateArrayElement(iter, pathKey) + if valueBytes == nil { + return newInvalidAny(path[i:]) + } + iter.ResetBytes(valueBytes) + case int32: + if '*' == pathKey { + return iter.readAny().Get(path[i:]...) + } + return newInvalidAny(path[i:]) + default: + return newInvalidAny(path[i:]) + } + } + if iter.Error != nil && iter.Error != io.EOF { + return &invalidAny{baseAny{}, iter.Error} + } + return iter.readAny() +} + +var anyType = reflect2.TypeOfPtr((*Any)(nil)).Elem() + +func createDecoderOfAny(ctx *ctx, typ reflect2.Type) ValDecoder { + if typ == anyType { + return &directAnyCodec{} + } + if typ.Implements(anyType) { + return &anyCodec{ + valType: typ, + } + } + return nil +} + +func createEncoderOfAny(ctx *ctx, typ reflect2.Type) ValEncoder { + if typ == anyType { + return &directAnyCodec{} + } + if typ.Implements(anyType) { + return &anyCodec{ + valType: typ, + } + } + return nil +} + +type anyCodec struct { + valType reflect2.Type +} + +func (codec *anyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + panic("not implemented") +} + +func (codec *anyCodec) Encode(ptr unsafe.Pointer, stream *Stream) { + obj := codec.valType.UnsafeIndirect(ptr) + any := obj.(Any) + any.WriteTo(stream) +} + +func (codec *anyCodec) IsEmpty(ptr unsafe.Pointer) bool { + obj := codec.valType.UnsafeIndirect(ptr) + any := obj.(Any) + return any.Size() == 0 +} + +type directAnyCodec struct { +} + +func (codec *directAnyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + *(*Any)(ptr) = iter.readAny() +} + +func (codec *directAnyCodec) Encode(ptr unsafe.Pointer, stream *Stream) { + any := *(*Any)(ptr) + if any == nil { + stream.WriteNil() + return + } + any.WriteTo(stream) +} + +func (codec *directAnyCodec) IsEmpty(ptr unsafe.Pointer) bool { + any := *(*Any)(ptr) + return any.Size() == 0 +} diff --git a/vendor/github.com/json-iterator/go/any_array.go b/vendor/github.com/json-iterator/go/any_array.go new file mode 100644 index 00000000..0449e9aa --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_array.go @@ -0,0 +1,278 @@ +package jsoniter + +import ( + "reflect" + "unsafe" +) + +type arrayLazyAny struct { + baseAny + cfg *frozenConfig + buf []byte + err error +} + +func (any *arrayLazyAny) ValueType() ValueType { + return ArrayValue +} + +func (any *arrayLazyAny) MustBeValid() Any { + return any +} + +func (any *arrayLazyAny) LastError() error { + return any.err +} + +func (any *arrayLazyAny) ToBool() bool { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + return iter.ReadArray() +} + +func (any *arrayLazyAny) ToInt() int { + if any.ToBool() { + return 1 + } + return 0 +} + +func (any *arrayLazyAny) ToInt32() int32 { + if any.ToBool() { + return 1 + } + return 0 +} + +func (any *arrayLazyAny) ToInt64() int64 { + if any.ToBool() { + return 1 + } + return 0 +} + +func (any *arrayLazyAny) ToUint() uint { + if any.ToBool() { + return 1 + } + return 0 +} + +func (any *arrayLazyAny) ToUint32() uint32 { + if any.ToBool() { + return 1 + } + return 0 +} + +func (any *arrayLazyAny) ToUint64() uint64 { + if any.ToBool() { + return 1 + } + return 0 +} + +func (any *arrayLazyAny) ToFloat32() float32 { + if any.ToBool() { + return 1 + } + return 0 +} + +func (any *arrayLazyAny) ToFloat64() float64 { + if any.ToBool() { + return 1 + } + return 0 +} + +func (any *arrayLazyAny) ToString() string { + return *(*string)(unsafe.Pointer(&any.buf)) +} + +func (any *arrayLazyAny) ToVal(val interface{}) { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + iter.ReadVal(val) +} + +func (any *arrayLazyAny) Get(path ...interface{}) Any { + if len(path) == 0 { + return any + } + switch firstPath := path[0].(type) { + case int: + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + valueBytes := locateArrayElement(iter, firstPath) + if valueBytes == nil { + return newInvalidAny(path) + } + iter.ResetBytes(valueBytes) + return locatePath(iter, path[1:]) + case int32: + if '*' == firstPath { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + arr := make([]Any, 0) + iter.ReadArrayCB(func(iter *Iterator) bool { + found := iter.readAny().Get(path[1:]...) + if found.ValueType() != InvalidValue { + arr = append(arr, found) + } + return true + }) + return wrapArray(arr) + } + return newInvalidAny(path) + default: + return newInvalidAny(path) + } +} + +func (any *arrayLazyAny) Size() int { + size := 0 + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + iter.ReadArrayCB(func(iter *Iterator) bool { + size++ + iter.Skip() + return true + }) + return size +} + +func (any *arrayLazyAny) WriteTo(stream *Stream) { + stream.Write(any.buf) +} + +func (any *arrayLazyAny) GetInterface() interface{} { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + return iter.Read() +} + +type arrayAny struct { + baseAny + val reflect.Value +} + +func wrapArray(val interface{}) *arrayAny { + return &arrayAny{baseAny{}, reflect.ValueOf(val)} +} + +func (any *arrayAny) ValueType() ValueType { + return ArrayValue +} + +func (any *arrayAny) MustBeValid() Any { + return any +} + +func (any *arrayAny) LastError() error { + return nil +} + +func (any *arrayAny) ToBool() bool { + return any.val.Len() != 0 +} + +func (any *arrayAny) ToInt() int { + if any.val.Len() == 0 { + return 0 + } + return 1 +} + +func (any *arrayAny) ToInt32() int32 { + if any.val.Len() == 0 { + return 0 + } + return 1 +} + +func (any *arrayAny) ToInt64() int64 { + if any.val.Len() == 0 { + return 0 + } + return 1 +} + +func (any *arrayAny) ToUint() uint { + if any.val.Len() == 0 { + return 0 + } + return 1 +} + +func (any *arrayAny) ToUint32() uint32 { + if any.val.Len() == 0 { + return 0 + } + return 1 +} + +func (any *arrayAny) ToUint64() uint64 { + if any.val.Len() == 0 { + return 0 + } + return 1 +} + +func (any *arrayAny) ToFloat32() float32 { + if any.val.Len() == 0 { + return 0 + } + return 1 +} + +func (any *arrayAny) ToFloat64() float64 { + if any.val.Len() == 0 { + return 0 + } + return 1 +} + +func (any *arrayAny) ToString() string { + str, _ := MarshalToString(any.val.Interface()) + return str +} + +func (any *arrayAny) Get(path ...interface{}) Any { + if len(path) == 0 { + return any + } + switch firstPath := path[0].(type) { + case int: + if firstPath < 0 || firstPath >= any.val.Len() { + return newInvalidAny(path) + } + return Wrap(any.val.Index(firstPath).Interface()) + case int32: + if '*' == firstPath { + mappedAll := make([]Any, 0) + for i := 0; i < any.val.Len(); i++ { + mapped := Wrap(any.val.Index(i).Interface()).Get(path[1:]...) + if mapped.ValueType() != InvalidValue { + mappedAll = append(mappedAll, mapped) + } + } + return wrapArray(mappedAll) + } + return newInvalidAny(path) + default: + return newInvalidAny(path) + } +} + +func (any *arrayAny) Size() int { + return any.val.Len() +} + +func (any *arrayAny) WriteTo(stream *Stream) { + stream.WriteVal(any.val) +} + +func (any *arrayAny) GetInterface() interface{} { + return any.val.Interface() +} diff --git a/vendor/github.com/json-iterator/go/any_bool.go b/vendor/github.com/json-iterator/go/any_bool.go new file mode 100644 index 00000000..9452324a --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_bool.go @@ -0,0 +1,137 @@ +package jsoniter + +type trueAny struct { + baseAny +} + +func (any *trueAny) LastError() error { + return nil +} + +func (any *trueAny) ToBool() bool { + return true +} + +func (any *trueAny) ToInt() int { + return 1 +} + +func (any *trueAny) ToInt32() int32 { + return 1 +} + +func (any *trueAny) ToInt64() int64 { + return 1 +} + +func (any *trueAny) ToUint() uint { + return 1 +} + +func (any *trueAny) ToUint32() uint32 { + return 1 +} + +func (any *trueAny) ToUint64() uint64 { + return 1 +} + +func (any *trueAny) ToFloat32() float32 { + return 1 +} + +func (any *trueAny) ToFloat64() float64 { + return 1 +} + +func (any *trueAny) ToString() string { + return "true" +} + +func (any *trueAny) WriteTo(stream *Stream) { + stream.WriteTrue() +} + +func (any *trueAny) Parse() *Iterator { + return nil +} + +func (any *trueAny) GetInterface() interface{} { + return true +} + +func (any *trueAny) ValueType() ValueType { + return BoolValue +} + +func (any *trueAny) MustBeValid() Any { + return any +} + +type falseAny struct { + baseAny +} + +func (any *falseAny) LastError() error { + return nil +} + +func (any *falseAny) ToBool() bool { + return false +} + +func (any *falseAny) ToInt() int { + return 0 +} + +func (any *falseAny) ToInt32() int32 { + return 0 +} + +func (any *falseAny) ToInt64() int64 { + return 0 +} + +func (any *falseAny) ToUint() uint { + return 0 +} + +func (any *falseAny) ToUint32() uint32 { + return 0 +} + +func (any *falseAny) ToUint64() uint64 { + return 0 +} + +func (any *falseAny) ToFloat32() float32 { + return 0 +} + +func (any *falseAny) ToFloat64() float64 { + return 0 +} + +func (any *falseAny) ToString() string { + return "false" +} + +func (any *falseAny) WriteTo(stream *Stream) { + stream.WriteFalse() +} + +func (any *falseAny) Parse() *Iterator { + return nil +} + +func (any *falseAny) GetInterface() interface{} { + return false +} + +func (any *falseAny) ValueType() ValueType { + return BoolValue +} + +func (any *falseAny) MustBeValid() Any { + return any +} diff --git a/vendor/github.com/json-iterator/go/any_float.go b/vendor/github.com/json-iterator/go/any_float.go new file mode 100644 index 00000000..35fdb094 --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_float.go @@ -0,0 +1,83 @@ +package jsoniter + +import ( + "strconv" +) + +type floatAny struct { + baseAny + val float64 +} + +func (any *floatAny) Parse() *Iterator { + return nil +} + +func (any *floatAny) ValueType() ValueType { + return NumberValue +} + +func (any *floatAny) MustBeValid() Any { + return any +} + +func (any *floatAny) LastError() error { + return nil +} + +func (any *floatAny) ToBool() bool { + return any.ToFloat64() != 0 +} + +func (any *floatAny) ToInt() int { + return int(any.val) +} + +func (any *floatAny) ToInt32() int32 { + return int32(any.val) +} + +func (any *floatAny) ToInt64() int64 { + return int64(any.val) +} + +func (any *floatAny) ToUint() uint { + if any.val > 0 { + return uint(any.val) + } + return 0 +} + +func (any *floatAny) ToUint32() uint32 { + if any.val > 0 { + return uint32(any.val) + } + return 0 +} + +func (any *floatAny) ToUint64() uint64 { + if any.val > 0 { + return uint64(any.val) + } + return 0 +} + +func (any *floatAny) ToFloat32() float32 { + return float32(any.val) +} + +func (any *floatAny) ToFloat64() float64 { + return any.val +} + +func (any *floatAny) ToString() string { + return strconv.FormatFloat(any.val, 'E', -1, 64) +} + +func (any *floatAny) WriteTo(stream *Stream) { + stream.WriteFloat64(any.val) +} + +func (any *floatAny) GetInterface() interface{} { + return any.val +} diff --git a/vendor/github.com/json-iterator/go/any_int32.go b/vendor/github.com/json-iterator/go/any_int32.go new file mode 100644 index 00000000..1b56f399 --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_int32.go @@ -0,0 +1,74 @@ +package jsoniter + +import ( + "strconv" +) + +type int32Any struct { + baseAny + val int32 +} + +func (any *int32Any) LastError() error { + return nil +} + +func (any *int32Any) ValueType() ValueType { + return NumberValue +} + +func (any *int32Any) MustBeValid() Any { + return any +} + +func (any *int32Any) ToBool() bool { + return any.val != 0 +} + +func (any *int32Any) ToInt() int { + return int(any.val) +} + +func (any *int32Any) ToInt32() int32 { + return any.val +} + +func (any *int32Any) ToInt64() int64 { + return int64(any.val) +} + +func (any *int32Any) ToUint() uint { + return uint(any.val) +} + +func (any *int32Any) ToUint32() uint32 { + return uint32(any.val) +} + +func (any *int32Any) ToUint64() uint64 { + return uint64(any.val) +} + +func (any *int32Any) ToFloat32() float32 { + return float32(any.val) +} + +func (any *int32Any) ToFloat64() float64 { + return float64(any.val) +} + +func (any *int32Any) ToString() string { + return strconv.FormatInt(int64(any.val), 10) +} + +func (any *int32Any) WriteTo(stream *Stream) { + stream.WriteInt32(any.val) +} + +func (any *int32Any) Parse() *Iterator { + return nil +} + +func (any *int32Any) GetInterface() interface{} { + return any.val +} diff --git a/vendor/github.com/json-iterator/go/any_int64.go b/vendor/github.com/json-iterator/go/any_int64.go new file mode 100644 index 00000000..c440d72b --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_int64.go @@ -0,0 +1,74 @@ +package jsoniter + +import ( + "strconv" +) + +type int64Any struct { + baseAny + val int64 +} + +func (any *int64Any) LastError() error { + return nil +} + +func (any *int64Any) ValueType() ValueType { + return NumberValue +} + +func (any *int64Any) MustBeValid() Any { + return any +} + +func (any *int64Any) ToBool() bool { + return any.val != 0 +} + +func (any *int64Any) ToInt() int { + return int(any.val) +} + +func (any *int64Any) ToInt32() int32 { + return int32(any.val) +} + +func (any *int64Any) ToInt64() int64 { + return any.val +} + +func (any *int64Any) ToUint() uint { + return uint(any.val) +} + +func (any *int64Any) ToUint32() uint32 { + return uint32(any.val) +} + +func (any *int64Any) ToUint64() uint64 { + return uint64(any.val) +} + +func (any *int64Any) ToFloat32() float32 { + return float32(any.val) +} + +func (any *int64Any) ToFloat64() float64 { + return float64(any.val) +} + +func (any *int64Any) ToString() string { + return strconv.FormatInt(any.val, 10) +} + +func (any *int64Any) WriteTo(stream *Stream) { + stream.WriteInt64(any.val) +} + +func (any *int64Any) Parse() *Iterator { + return nil +} + +func (any *int64Any) GetInterface() interface{} { + return any.val +} diff --git a/vendor/github.com/json-iterator/go/any_invalid.go b/vendor/github.com/json-iterator/go/any_invalid.go new file mode 100644 index 00000000..1d859eac --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_invalid.go @@ -0,0 +1,82 @@ +package jsoniter + +import "fmt" + +type invalidAny struct { + baseAny + err error +} + +func newInvalidAny(path []interface{}) *invalidAny { + return &invalidAny{baseAny{}, fmt.Errorf("%v not found", path)} +} + +func (any *invalidAny) LastError() error { + return any.err +} + +func (any *invalidAny) ValueType() ValueType { + return InvalidValue +} + +func (any *invalidAny) MustBeValid() Any { + panic(any.err) +} + +func (any *invalidAny) ToBool() bool { + return false +} + +func (any *invalidAny) ToInt() int { + return 0 +} + +func (any *invalidAny) ToInt32() int32 { + return 0 +} + +func (any *invalidAny) ToInt64() int64 { + return 0 +} + +func (any *invalidAny) ToUint() uint { + return 0 +} + +func (any *invalidAny) ToUint32() uint32 { + return 0 +} + +func (any *invalidAny) ToUint64() uint64 { + return 0 +} + +func (any *invalidAny) ToFloat32() float32 { + return 0 +} + +func (any *invalidAny) ToFloat64() float64 { + return 0 +} + +func (any *invalidAny) ToString() string { + return "" +} + +func (any *invalidAny) WriteTo(stream *Stream) { +} + +func (any *invalidAny) Get(path ...interface{}) Any { + if any.err == nil { + return &invalidAny{baseAny{}, fmt.Errorf("get %v from invalid", path)} + } + return &invalidAny{baseAny{}, fmt.Errorf("%v, get %v from invalid", any.err, path)} +} + +func (any *invalidAny) Parse() *Iterator { + return nil +} + +func (any *invalidAny) GetInterface() interface{} { + return nil +} diff --git a/vendor/github.com/json-iterator/go/any_nil.go b/vendor/github.com/json-iterator/go/any_nil.go new file mode 100644 index 00000000..d04cb54c --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_nil.go @@ -0,0 +1,69 @@ +package jsoniter + +type nilAny struct { + baseAny +} + +func (any *nilAny) LastError() error { + return nil +} + +func (any *nilAny) ValueType() ValueType { + return NilValue +} + +func (any *nilAny) MustBeValid() Any { + return any +} + +func (any *nilAny) ToBool() bool { + return false +} + +func (any *nilAny) ToInt() int { + return 0 +} + +func (any *nilAny) ToInt32() int32 { + return 0 +} + +func (any *nilAny) ToInt64() int64 { + return 0 +} + +func (any *nilAny) ToUint() uint { + return 0 +} + +func (any *nilAny) ToUint32() uint32 { + return 0 +} + +func (any *nilAny) ToUint64() uint64 { + return 0 +} + +func (any *nilAny) ToFloat32() float32 { + return 0 +} + +func (any *nilAny) ToFloat64() float64 { + return 0 +} + +func (any *nilAny) ToString() string { + return "" +} + +func (any *nilAny) WriteTo(stream *Stream) { + stream.WriteNil() +} + +func (any *nilAny) Parse() *Iterator { + return nil +} + +func (any *nilAny) GetInterface() interface{} { + return nil +} diff --git a/vendor/github.com/json-iterator/go/any_number.go b/vendor/github.com/json-iterator/go/any_number.go new file mode 100644 index 00000000..9d1e901a --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_number.go @@ -0,0 +1,123 @@ +package jsoniter + +import ( + "io" + "unsafe" +) + +type numberLazyAny struct { + baseAny + cfg *frozenConfig + buf []byte + err error +} + +func (any *numberLazyAny) ValueType() ValueType { + return NumberValue +} + +func (any *numberLazyAny) MustBeValid() Any { + return any +} + +func (any *numberLazyAny) LastError() error { + return any.err +} + +func (any *numberLazyAny) ToBool() bool { + return any.ToFloat64() != 0 +} + +func (any *numberLazyAny) ToInt() int { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + val := iter.ReadInt() + if iter.Error != nil && iter.Error != io.EOF { + any.err = iter.Error + } + return val +} + +func (any *numberLazyAny) ToInt32() int32 { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + val := iter.ReadInt32() + if iter.Error != nil && iter.Error != io.EOF { + any.err = iter.Error + } + return val +} + +func (any *numberLazyAny) ToInt64() int64 { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + val := iter.ReadInt64() + if iter.Error != nil && iter.Error != io.EOF { + any.err = iter.Error + } + return val +} + +func (any *numberLazyAny) ToUint() uint { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + val := iter.ReadUint() + if iter.Error != nil && iter.Error != io.EOF { + any.err = iter.Error + } + return val +} + +func (any *numberLazyAny) ToUint32() uint32 { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + val := iter.ReadUint32() + if iter.Error != nil && iter.Error != io.EOF { + any.err = iter.Error + } + return val +} + +func (any *numberLazyAny) ToUint64() uint64 { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + val := iter.ReadUint64() + if iter.Error != nil && iter.Error != io.EOF { + any.err = iter.Error + } + return val +} + +func (any *numberLazyAny) ToFloat32() float32 { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + val := iter.ReadFloat32() + if iter.Error != nil && iter.Error != io.EOF { + any.err = iter.Error + } + return val +} + +func (any *numberLazyAny) ToFloat64() float64 { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + val := iter.ReadFloat64() + if iter.Error != nil && iter.Error != io.EOF { + any.err = iter.Error + } + return val +} + +func (any *numberLazyAny) ToString() string { + return *(*string)(unsafe.Pointer(&any.buf)) +} + +func (any *numberLazyAny) WriteTo(stream *Stream) { + stream.Write(any.buf) +} + +func (any *numberLazyAny) GetInterface() interface{} { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + return iter.Read() +} diff --git a/vendor/github.com/json-iterator/go/any_object.go b/vendor/github.com/json-iterator/go/any_object.go new file mode 100644 index 00000000..c44ef5c9 --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_object.go @@ -0,0 +1,374 @@ +package jsoniter + +import ( + "reflect" + "unsafe" +) + +type objectLazyAny struct { + baseAny + cfg *frozenConfig + buf []byte + err error +} + +func (any *objectLazyAny) ValueType() ValueType { + return ObjectValue +} + +func (any *objectLazyAny) MustBeValid() Any { + return any +} + +func (any *objectLazyAny) LastError() error { + return any.err +} + +func (any *objectLazyAny) ToBool() bool { + return true +} + +func (any *objectLazyAny) ToInt() int { + return 0 +} + +func (any *objectLazyAny) ToInt32() int32 { + return 0 +} + +func (any *objectLazyAny) ToInt64() int64 { + return 0 +} + +func (any *objectLazyAny) ToUint() uint { + return 0 +} + +func (any *objectLazyAny) ToUint32() uint32 { + return 0 +} + +func (any *objectLazyAny) ToUint64() uint64 { + return 0 +} + +func (any *objectLazyAny) ToFloat32() float32 { + return 0 +} + +func (any *objectLazyAny) ToFloat64() float64 { + return 0 +} + +func (any *objectLazyAny) ToString() string { + return *(*string)(unsafe.Pointer(&any.buf)) +} + +func (any *objectLazyAny) ToVal(obj interface{}) { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + iter.ReadVal(obj) +} + +func (any *objectLazyAny) Get(path ...interface{}) Any { + if len(path) == 0 { + return any + } + switch firstPath := path[0].(type) { + case string: + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + valueBytes := locateObjectField(iter, firstPath) + if valueBytes == nil { + return newInvalidAny(path) + } + iter.ResetBytes(valueBytes) + return locatePath(iter, path[1:]) + case int32: + if '*' == firstPath { + mappedAll := map[string]Any{} + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + iter.ReadMapCB(func(iter *Iterator, field string) bool { + mapped := locatePath(iter, path[1:]) + if mapped.ValueType() != InvalidValue { + mappedAll[field] = mapped + } + return true + }) + return wrapMap(mappedAll) + } + return newInvalidAny(path) + default: + return newInvalidAny(path) + } +} + +func (any *objectLazyAny) Keys() []string { + keys := []string{} + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + iter.ReadMapCB(func(iter *Iterator, field string) bool { + iter.Skip() + keys = append(keys, field) + return true + }) + return keys +} + +func (any *objectLazyAny) Size() int { + size := 0 + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + iter.ReadObjectCB(func(iter *Iterator, field string) bool { + iter.Skip() + size++ + return true + }) + return size +} + +func (any *objectLazyAny) WriteTo(stream *Stream) { + stream.Write(any.buf) +} + +func (any *objectLazyAny) GetInterface() interface{} { + iter := any.cfg.BorrowIterator(any.buf) + defer any.cfg.ReturnIterator(iter) + return iter.Read() +} + +type objectAny struct { + baseAny + err error + val reflect.Value +} + +func wrapStruct(val interface{}) *objectAny { + return &objectAny{baseAny{}, nil, reflect.ValueOf(val)} +} + +func (any *objectAny) ValueType() ValueType { + return ObjectValue +} + +func (any *objectAny) MustBeValid() Any { + return any +} + +func (any *objectAny) Parse() *Iterator { + return nil +} + +func (any *objectAny) LastError() error { + return any.err +} + +func (any *objectAny) ToBool() bool { + return any.val.NumField() != 0 +} + +func (any *objectAny) ToInt() int { + return 0 +} + +func (any *objectAny) ToInt32() int32 { + return 0 +} + +func (any *objectAny) ToInt64() int64 { + return 0 +} + +func (any *objectAny) ToUint() uint { + return 0 +} + +func (any *objectAny) ToUint32() uint32 { + return 0 +} + +func (any *objectAny) ToUint64() uint64 { + return 0 +} + +func (any *objectAny) ToFloat32() float32 { + return 0 +} + +func (any *objectAny) ToFloat64() float64 { + return 0 +} + +func (any *objectAny) ToString() string { + str, err := MarshalToString(any.val.Interface()) + any.err = err + return str +} + +func (any *objectAny) Get(path ...interface{}) Any { + if len(path) == 0 { + return any + } + switch firstPath := path[0].(type) { + case string: + field := any.val.FieldByName(firstPath) + if !field.IsValid() { + return newInvalidAny(path) + } + return Wrap(field.Interface()) + case int32: + if '*' == firstPath { + mappedAll := map[string]Any{} + for i := 0; i < any.val.NumField(); i++ { + field := any.val.Field(i) + if field.CanInterface() { + mapped := Wrap(field.Interface()).Get(path[1:]...) + if mapped.ValueType() != InvalidValue { + mappedAll[any.val.Type().Field(i).Name] = mapped + } + } + } + return wrapMap(mappedAll) + } + return newInvalidAny(path) + default: + return newInvalidAny(path) + } +} + +func (any *objectAny) Keys() []string { + keys := make([]string, 0, any.val.NumField()) + for i := 0; i < any.val.NumField(); i++ { + keys = append(keys, any.val.Type().Field(i).Name) + } + return keys +} + +func (any *objectAny) Size() int { + return any.val.NumField() +} + +func (any *objectAny) WriteTo(stream *Stream) { + stream.WriteVal(any.val) +} + +func (any *objectAny) GetInterface() interface{} { + return any.val.Interface() +} + +type mapAny struct { + baseAny + err error + val reflect.Value +} + +func wrapMap(val interface{}) *mapAny { + return &mapAny{baseAny{}, nil, reflect.ValueOf(val)} +} + +func (any *mapAny) ValueType() ValueType { + return ObjectValue +} + +func (any *mapAny) MustBeValid() Any { + return any +} + +func (any *mapAny) Parse() *Iterator { + return nil +} + +func (any *mapAny) LastError() error { + return any.err +} + +func (any *mapAny) ToBool() bool { + return true +} + +func (any *mapAny) ToInt() int { + return 0 +} + +func (any *mapAny) ToInt32() int32 { + return 0 +} + +func (any *mapAny) ToInt64() int64 { + return 0 +} + +func (any *mapAny) ToUint() uint { + return 0 +} + +func (any *mapAny) ToUint32() uint32 { + return 0 +} + +func (any *mapAny) ToUint64() uint64 { + return 0 +} + +func (any *mapAny) ToFloat32() float32 { + return 0 +} + +func (any *mapAny) ToFloat64() float64 { + return 0 +} + +func (any *mapAny) ToString() string { + str, err := MarshalToString(any.val.Interface()) + any.err = err + return str +} + +func (any *mapAny) Get(path ...interface{}) Any { + if len(path) == 0 { + return any + } + switch firstPath := path[0].(type) { + case int32: + if '*' == firstPath { + mappedAll := map[string]Any{} + for _, key := range any.val.MapKeys() { + keyAsStr := key.String() + element := Wrap(any.val.MapIndex(key).Interface()) + mapped := element.Get(path[1:]...) + if mapped.ValueType() != InvalidValue { + mappedAll[keyAsStr] = mapped + } + } + return wrapMap(mappedAll) + } + return newInvalidAny(path) + default: + value := any.val.MapIndex(reflect.ValueOf(firstPath)) + if !value.IsValid() { + return newInvalidAny(path) + } + return Wrap(value.Interface()) + } +} + +func (any *mapAny) Keys() []string { + keys := make([]string, 0, any.val.Len()) + for _, key := range any.val.MapKeys() { + keys = append(keys, key.String()) + } + return keys +} + +func (any *mapAny) Size() int { + return any.val.Len() +} + +func (any *mapAny) WriteTo(stream *Stream) { + stream.WriteVal(any.val) +} + +func (any *mapAny) GetInterface() interface{} { + return any.val.Interface() +} diff --git a/vendor/github.com/json-iterator/go/any_str.go b/vendor/github.com/json-iterator/go/any_str.go new file mode 100644 index 00000000..1f12f661 --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_str.go @@ -0,0 +1,166 @@ +package jsoniter + +import ( + "fmt" + "strconv" +) + +type stringAny struct { + baseAny + val string +} + +func (any *stringAny) Get(path ...interface{}) Any { + if len(path) == 0 { + return any + } + return &invalidAny{baseAny{}, fmt.Errorf("GetIndex %v from simple value", path)} +} + +func (any *stringAny) Parse() *Iterator { + return nil +} + +func (any *stringAny) ValueType() ValueType { + return StringValue +} + +func (any *stringAny) MustBeValid() Any { + return any +} + +func (any *stringAny) LastError() error { + return nil +} + +func (any *stringAny) ToBool() bool { + str := any.ToString() + if str == "0" { + return false + } + for _, c := range str { + switch c { + case ' ', '\n', '\r', '\t': + default: + return true + } + } + return false +} + +func (any *stringAny) ToInt() int { + return int(any.ToInt64()) + +} + +func (any *stringAny) ToInt32() int32 { + return int32(any.ToInt64()) +} + +func (any *stringAny) ToInt64() int64 { + if any.val == "" { + return 0 + } + + flag := 1 + startPos := 0 + if any.val[0] == '+' || any.val[0] == '-' { + startPos = 1 + } + + if any.val[0] == '-' { + flag = -1 + } + + endPos := startPos + for i := startPos; i < len(any.val); i++ { + if any.val[i] >= '0' && any.val[i] <= '9' { + endPos = i + 1 + } else { + break + } + } + parsed, _ := strconv.ParseInt(any.val[startPos:endPos], 10, 64) + return int64(flag) * parsed +} + +func (any *stringAny) ToUint() uint { + return uint(any.ToUint64()) +} + +func (any *stringAny) ToUint32() uint32 { + return uint32(any.ToUint64()) +} + +func (any *stringAny) ToUint64() uint64 { + if any.val == "" { + return 0 + } + + startPos := 0 + + if any.val[0] == '-' { + return 0 + } + if any.val[0] == '+' { + startPos = 1 + } + + endPos := startPos + for i := startPos; i < len(any.val); i++ { + if any.val[i] >= '0' && any.val[i] <= '9' { + endPos = i + 1 + } else { + break + } + } + parsed, _ := strconv.ParseUint(any.val[startPos:endPos], 10, 64) + return parsed +} + +func (any *stringAny) ToFloat32() float32 { + return float32(any.ToFloat64()) +} + +func (any *stringAny) ToFloat64() float64 { + if len(any.val) == 0 { + return 0 + } + + // first char invalid + if any.val[0] != '+' && any.val[0] != '-' && (any.val[0] > '9' || any.val[0] < '0') { + return 0 + } + + // extract valid num expression from string + // eg 123true => 123, -12.12xxa => -12.12 + endPos := 1 + for i := 1; i < len(any.val); i++ { + if any.val[i] == '.' || any.val[i] == 'e' || any.val[i] == 'E' || any.val[i] == '+' || any.val[i] == '-' { + endPos = i + 1 + continue + } + + // end position is the first char which is not digit + if any.val[i] >= '0' && any.val[i] <= '9' { + endPos = i + 1 + } else { + endPos = i + break + } + } + parsed, _ := strconv.ParseFloat(any.val[:endPos], 64) + return parsed +} + +func (any *stringAny) ToString() string { + return any.val +} + +func (any *stringAny) WriteTo(stream *Stream) { + stream.WriteString(any.val) +} + +func (any *stringAny) GetInterface() interface{} { + return any.val +} diff --git a/vendor/github.com/json-iterator/go/any_uint32.go b/vendor/github.com/json-iterator/go/any_uint32.go new file mode 100644 index 00000000..656bbd33 --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_uint32.go @@ -0,0 +1,74 @@ +package jsoniter + +import ( + "strconv" +) + +type uint32Any struct { + baseAny + val uint32 +} + +func (any *uint32Any) LastError() error { + return nil +} + +func (any *uint32Any) ValueType() ValueType { + return NumberValue +} + +func (any *uint32Any) MustBeValid() Any { + return any +} + +func (any *uint32Any) ToBool() bool { + return any.val != 0 +} + +func (any *uint32Any) ToInt() int { + return int(any.val) +} + +func (any *uint32Any) ToInt32() int32 { + return int32(any.val) +} + +func (any *uint32Any) ToInt64() int64 { + return int64(any.val) +} + +func (any *uint32Any) ToUint() uint { + return uint(any.val) +} + +func (any *uint32Any) ToUint32() uint32 { + return any.val +} + +func (any *uint32Any) ToUint64() uint64 { + return uint64(any.val) +} + +func (any *uint32Any) ToFloat32() float32 { + return float32(any.val) +} + +func (any *uint32Any) ToFloat64() float64 { + return float64(any.val) +} + +func (any *uint32Any) ToString() string { + return strconv.FormatInt(int64(any.val), 10) +} + +func (any *uint32Any) WriteTo(stream *Stream) { + stream.WriteUint32(any.val) +} + +func (any *uint32Any) Parse() *Iterator { + return nil +} + +func (any *uint32Any) GetInterface() interface{} { + return any.val +} diff --git a/vendor/github.com/json-iterator/go/any_uint64.go b/vendor/github.com/json-iterator/go/any_uint64.go new file mode 100644 index 00000000..7df2fce3 --- /dev/null +++ b/vendor/github.com/json-iterator/go/any_uint64.go @@ -0,0 +1,74 @@ +package jsoniter + +import ( + "strconv" +) + +type uint64Any struct { + baseAny + val uint64 +} + +func (any *uint64Any) LastError() error { + return nil +} + +func (any *uint64Any) ValueType() ValueType { + return NumberValue +} + +func (any *uint64Any) MustBeValid() Any { + return any +} + +func (any *uint64Any) ToBool() bool { + return any.val != 0 +} + +func (any *uint64Any) ToInt() int { + return int(any.val) +} + +func (any *uint64Any) ToInt32() int32 { + return int32(any.val) +} + +func (any *uint64Any) ToInt64() int64 { + return int64(any.val) +} + +func (any *uint64Any) ToUint() uint { + return uint(any.val) +} + +func (any *uint64Any) ToUint32() uint32 { + return uint32(any.val) +} + +func (any *uint64Any) ToUint64() uint64 { + return any.val +} + +func (any *uint64Any) ToFloat32() float32 { + return float32(any.val) +} + +func (any *uint64Any) ToFloat64() float64 { + return float64(any.val) +} + +func (any *uint64Any) ToString() string { + return strconv.FormatUint(any.val, 10) +} + +func (any *uint64Any) WriteTo(stream *Stream) { + stream.WriteUint64(any.val) +} + +func (any *uint64Any) Parse() *Iterator { + return nil +} + +func (any *uint64Any) GetInterface() interface{} { + return any.val +} diff --git a/vendor/github.com/json-iterator/go/build.sh b/vendor/github.com/json-iterator/go/build.sh new file mode 100644 index 00000000..b45ef688 --- /dev/null +++ b/vendor/github.com/json-iterator/go/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e +set -x + +if [ ! -d /tmp/build-golang/src/github.com/json-iterator ]; then + mkdir -p /tmp/build-golang/src/github.com/json-iterator + ln -s $PWD /tmp/build-golang/src/github.com/json-iterator/go +fi +export GOPATH=/tmp/build-golang +go get -u github.com/golang/dep/cmd/dep +cd /tmp/build-golang/src/github.com/json-iterator/go +exec $GOPATH/bin/dep ensure -update diff --git a/vendor/github.com/json-iterator/go/config.go b/vendor/github.com/json-iterator/go/config.go new file mode 100644 index 00000000..2adcdc3b --- /dev/null +++ b/vendor/github.com/json-iterator/go/config.go @@ -0,0 +1,375 @@ +package jsoniter + +import ( + "encoding/json" + "io" + "reflect" + "sync" + "unsafe" + + "github.com/modern-go/concurrent" + "github.com/modern-go/reflect2" +) + +// Config customize how the API should behave. +// The API is created from Config by Froze. +type Config struct { + IndentionStep int + MarshalFloatWith6Digits bool + EscapeHTML bool + SortMapKeys bool + UseNumber bool + DisallowUnknownFields bool + TagKey string + OnlyTaggedField bool + ValidateJsonRawMessage bool + ObjectFieldMustBeSimpleString bool + CaseSensitive bool +} + +// API the public interface of this package. +// Primary Marshal and Unmarshal. +type API interface { + IteratorPool + StreamPool + MarshalToString(v interface{}) (string, error) + Marshal(v interface{}) ([]byte, error) + MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) + UnmarshalFromString(str string, v interface{}) error + Unmarshal(data []byte, v interface{}) error + Get(data []byte, path ...interface{}) Any + NewEncoder(writer io.Writer) *Encoder + NewDecoder(reader io.Reader) *Decoder + Valid(data []byte) bool + RegisterExtension(extension Extension) + DecoderOf(typ reflect2.Type) ValDecoder + EncoderOf(typ reflect2.Type) ValEncoder +} + +// ConfigDefault the default API +var ConfigDefault = Config{ + EscapeHTML: true, +}.Froze() + +// ConfigCompatibleWithStandardLibrary tries to be 100% compatible with standard library behavior +var ConfigCompatibleWithStandardLibrary = Config{ + EscapeHTML: true, + SortMapKeys: true, + ValidateJsonRawMessage: true, +}.Froze() + +// ConfigFastest marshals float with only 6 digits precision +var ConfigFastest = Config{ + EscapeHTML: false, + MarshalFloatWith6Digits: true, // will lose precession + ObjectFieldMustBeSimpleString: true, // do not unescape object field +}.Froze() + +type frozenConfig struct { + configBeforeFrozen Config + sortMapKeys bool + indentionStep int + objectFieldMustBeSimpleString bool + onlyTaggedField bool + disallowUnknownFields bool + decoderCache *concurrent.Map + encoderCache *concurrent.Map + encoderExtension Extension + decoderExtension Extension + extraExtensions []Extension + streamPool *sync.Pool + iteratorPool *sync.Pool + caseSensitive bool +} + +func (cfg *frozenConfig) initCache() { + cfg.decoderCache = concurrent.NewMap() + cfg.encoderCache = concurrent.NewMap() +} + +func (cfg *frozenConfig) addDecoderToCache(cacheKey uintptr, decoder ValDecoder) { + cfg.decoderCache.Store(cacheKey, decoder) +} + +func (cfg *frozenConfig) addEncoderToCache(cacheKey uintptr, encoder ValEncoder) { + cfg.encoderCache.Store(cacheKey, encoder) +} + +func (cfg *frozenConfig) getDecoderFromCache(cacheKey uintptr) ValDecoder { + decoder, found := cfg.decoderCache.Load(cacheKey) + if found { + return decoder.(ValDecoder) + } + return nil +} + +func (cfg *frozenConfig) getEncoderFromCache(cacheKey uintptr) ValEncoder { + encoder, found := cfg.encoderCache.Load(cacheKey) + if found { + return encoder.(ValEncoder) + } + return nil +} + +var cfgCache = concurrent.NewMap() + +func getFrozenConfigFromCache(cfg Config) *frozenConfig { + obj, found := cfgCache.Load(cfg) + if found { + return obj.(*frozenConfig) + } + return nil +} + +func addFrozenConfigToCache(cfg Config, frozenConfig *frozenConfig) { + cfgCache.Store(cfg, frozenConfig) +} + +// Froze forge API from config +func (cfg Config) Froze() API { + api := &frozenConfig{ + sortMapKeys: cfg.SortMapKeys, + indentionStep: cfg.IndentionStep, + objectFieldMustBeSimpleString: cfg.ObjectFieldMustBeSimpleString, + onlyTaggedField: cfg.OnlyTaggedField, + disallowUnknownFields: cfg.DisallowUnknownFields, + caseSensitive: cfg.CaseSensitive, + } + api.streamPool = &sync.Pool{ + New: func() interface{} { + return NewStream(api, nil, 512) + }, + } + api.iteratorPool = &sync.Pool{ + New: func() interface{} { + return NewIterator(api) + }, + } + api.initCache() + encoderExtension := EncoderExtension{} + decoderExtension := DecoderExtension{} + if cfg.MarshalFloatWith6Digits { + api.marshalFloatWith6Digits(encoderExtension) + } + if cfg.EscapeHTML { + api.escapeHTML(encoderExtension) + } + if cfg.UseNumber { + api.useNumber(decoderExtension) + } + if cfg.ValidateJsonRawMessage { + api.validateJsonRawMessage(encoderExtension) + } + api.encoderExtension = encoderExtension + api.decoderExtension = decoderExtension + api.configBeforeFrozen = cfg + return api +} + +func (cfg Config) frozeWithCacheReuse(extraExtensions []Extension) *frozenConfig { + api := getFrozenConfigFromCache(cfg) + if api != nil { + return api + } + api = cfg.Froze().(*frozenConfig) + for _, extension := range extraExtensions { + api.RegisterExtension(extension) + } + addFrozenConfigToCache(cfg, api) + return api +} + +func (cfg *frozenConfig) validateJsonRawMessage(extension EncoderExtension) { + encoder := &funcEncoder{func(ptr unsafe.Pointer, stream *Stream) { + rawMessage := *(*json.RawMessage)(ptr) + iter := cfg.BorrowIterator([]byte(rawMessage)) + defer cfg.ReturnIterator(iter) + iter.Read() + if iter.Error != nil && iter.Error != io.EOF { + stream.WriteRaw("null") + } else { + stream.WriteRaw(string(rawMessage)) + } + }, func(ptr unsafe.Pointer) bool { + return len(*((*json.RawMessage)(ptr))) == 0 + }} + extension[reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem()] = encoder + extension[reflect2.TypeOfPtr((*RawMessage)(nil)).Elem()] = encoder +} + +func (cfg *frozenConfig) useNumber(extension DecoderExtension) { + extension[reflect2.TypeOfPtr((*interface{})(nil)).Elem()] = &funcDecoder{func(ptr unsafe.Pointer, iter *Iterator) { + exitingValue := *((*interface{})(ptr)) + if exitingValue != nil && reflect.TypeOf(exitingValue).Kind() == reflect.Ptr { + iter.ReadVal(exitingValue) + return + } + if iter.WhatIsNext() == NumberValue { + *((*interface{})(ptr)) = json.Number(iter.readNumberAsString()) + } else { + *((*interface{})(ptr)) = iter.Read() + } + }} +} +func (cfg *frozenConfig) getTagKey() string { + tagKey := cfg.configBeforeFrozen.TagKey + if tagKey == "" { + return "json" + } + return tagKey +} + +func (cfg *frozenConfig) RegisterExtension(extension Extension) { + cfg.extraExtensions = append(cfg.extraExtensions, extension) + copied := cfg.configBeforeFrozen + cfg.configBeforeFrozen = copied +} + +type lossyFloat32Encoder struct { +} + +func (encoder *lossyFloat32Encoder) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteFloat32Lossy(*((*float32)(ptr))) +} + +func (encoder *lossyFloat32Encoder) IsEmpty(ptr unsafe.Pointer) bool { + return *((*float32)(ptr)) == 0 +} + +type lossyFloat64Encoder struct { +} + +func (encoder *lossyFloat64Encoder) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteFloat64Lossy(*((*float64)(ptr))) +} + +func (encoder *lossyFloat64Encoder) IsEmpty(ptr unsafe.Pointer) bool { + return *((*float64)(ptr)) == 0 +} + +// EnableLossyFloatMarshalling keeps 10**(-6) precision +// for float variables for better performance. +func (cfg *frozenConfig) marshalFloatWith6Digits(extension EncoderExtension) { + // for better performance + extension[reflect2.TypeOfPtr((*float32)(nil)).Elem()] = &lossyFloat32Encoder{} + extension[reflect2.TypeOfPtr((*float64)(nil)).Elem()] = &lossyFloat64Encoder{} +} + +type htmlEscapedStringEncoder struct { +} + +func (encoder *htmlEscapedStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + str := *((*string)(ptr)) + stream.WriteStringWithHTMLEscaped(str) +} + +func (encoder *htmlEscapedStringEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return *((*string)(ptr)) == "" +} + +func (cfg *frozenConfig) escapeHTML(encoderExtension EncoderExtension) { + encoderExtension[reflect2.TypeOfPtr((*string)(nil)).Elem()] = &htmlEscapedStringEncoder{} +} + +func (cfg *frozenConfig) cleanDecoders() { + typeDecoders = map[string]ValDecoder{} + fieldDecoders = map[string]ValDecoder{} + *cfg = *(cfg.configBeforeFrozen.Froze().(*frozenConfig)) +} + +func (cfg *frozenConfig) cleanEncoders() { + typeEncoders = map[string]ValEncoder{} + fieldEncoders = map[string]ValEncoder{} + *cfg = *(cfg.configBeforeFrozen.Froze().(*frozenConfig)) +} + +func (cfg *frozenConfig) MarshalToString(v interface{}) (string, error) { + stream := cfg.BorrowStream(nil) + defer cfg.ReturnStream(stream) + stream.WriteVal(v) + if stream.Error != nil { + return "", stream.Error + } + return string(stream.Buffer()), nil +} + +func (cfg *frozenConfig) Marshal(v interface{}) ([]byte, error) { + stream := cfg.BorrowStream(nil) + defer cfg.ReturnStream(stream) + stream.WriteVal(v) + if stream.Error != nil { + return nil, stream.Error + } + result := stream.Buffer() + copied := make([]byte, len(result)) + copy(copied, result) + return copied, nil +} + +func (cfg *frozenConfig) MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { + if prefix != "" { + panic("prefix is not supported") + } + for _, r := range indent { + if r != ' ' { + panic("indent can only be space") + } + } + newCfg := cfg.configBeforeFrozen + newCfg.IndentionStep = len(indent) + return newCfg.frozeWithCacheReuse(cfg.extraExtensions).Marshal(v) +} + +func (cfg *frozenConfig) UnmarshalFromString(str string, v interface{}) error { + data := []byte(str) + iter := cfg.BorrowIterator(data) + defer cfg.ReturnIterator(iter) + iter.ReadVal(v) + c := iter.nextToken() + if c == 0 { + if iter.Error == io.EOF { + return nil + } + return iter.Error + } + iter.ReportError("Unmarshal", "there are bytes left after unmarshal") + return iter.Error +} + +func (cfg *frozenConfig) Get(data []byte, path ...interface{}) Any { + iter := cfg.BorrowIterator(data) + defer cfg.ReturnIterator(iter) + return locatePath(iter, path) +} + +func (cfg *frozenConfig) Unmarshal(data []byte, v interface{}) error { + iter := cfg.BorrowIterator(data) + defer cfg.ReturnIterator(iter) + iter.ReadVal(v) + c := iter.nextToken() + if c == 0 { + if iter.Error == io.EOF { + return nil + } + return iter.Error + } + iter.ReportError("Unmarshal", "there are bytes left after unmarshal") + return iter.Error +} + +func (cfg *frozenConfig) NewEncoder(writer io.Writer) *Encoder { + stream := NewStream(cfg, writer, 512) + return &Encoder{stream} +} + +func (cfg *frozenConfig) NewDecoder(reader io.Reader) *Decoder { + iter := Parse(cfg, reader, 512) + return &Decoder{iter} +} + +func (cfg *frozenConfig) Valid(data []byte) bool { + iter := cfg.BorrowIterator(data) + defer cfg.ReturnIterator(iter) + iter.Skip() + return iter.Error == nil +} diff --git a/vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md b/vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md new file mode 100644 index 00000000..3095662b --- /dev/null +++ b/vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md @@ -0,0 +1,7 @@ +| json type \ dest type | bool | int | uint | float |string| +| --- | --- | --- | --- |--|--| +| number | positive => true
negative => true
zero => false| 23.2 => 23
-32.1 => -32| 12.1 => 12
-12.1 => 0|as normal|same as origin| +| string | empty string => false
string "0" => false
other strings => true | "123.32" => 123
"-123.4" => -123
"123.23xxxw" => 123
"abcde12" => 0
"-32.1" => -32| 13.2 => 13
-1.1 => 0 |12.1 => 12.1
-12.3 => -12.3
12.4xxa => 12.4
+1.1e2 =>110 |same as origin| +| bool | true => true
false => false| true => 1
false => 0 | true => 1
false => 0 |true => 1
false => 0|true => "true"
false => "false"| +| object | true | 0 | 0 |0|originnal json| +| array | empty array => false
nonempty array => true| [] => 0
[1,2] => 1 | [] => 0
[1,2] => 1 |[] => 0
[1,2] => 1|original json| \ No newline at end of file diff --git a/vendor/github.com/json-iterator/go/iter.go b/vendor/github.com/json-iterator/go/iter.go new file mode 100644 index 00000000..29b31cf7 --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter.go @@ -0,0 +1,349 @@ +package jsoniter + +import ( + "encoding/json" + "fmt" + "io" +) + +// ValueType the type for JSON element +type ValueType int + +const ( + // InvalidValue invalid JSON element + InvalidValue ValueType = iota + // StringValue JSON element "string" + StringValue + // NumberValue JSON element 100 or 0.10 + NumberValue + // NilValue JSON element null + NilValue + // BoolValue JSON element true or false + BoolValue + // ArrayValue JSON element [] + ArrayValue + // ObjectValue JSON element {} + ObjectValue +) + +var hexDigits []byte +var valueTypes []ValueType + +func init() { + hexDigits = make([]byte, 256) + for i := 0; i < len(hexDigits); i++ { + hexDigits[i] = 255 + } + for i := '0'; i <= '9'; i++ { + hexDigits[i] = byte(i - '0') + } + for i := 'a'; i <= 'f'; i++ { + hexDigits[i] = byte((i - 'a') + 10) + } + for i := 'A'; i <= 'F'; i++ { + hexDigits[i] = byte((i - 'A') + 10) + } + valueTypes = make([]ValueType, 256) + for i := 0; i < len(valueTypes); i++ { + valueTypes[i] = InvalidValue + } + valueTypes['"'] = StringValue + valueTypes['-'] = NumberValue + valueTypes['0'] = NumberValue + valueTypes['1'] = NumberValue + valueTypes['2'] = NumberValue + valueTypes['3'] = NumberValue + valueTypes['4'] = NumberValue + valueTypes['5'] = NumberValue + valueTypes['6'] = NumberValue + valueTypes['7'] = NumberValue + valueTypes['8'] = NumberValue + valueTypes['9'] = NumberValue + valueTypes['t'] = BoolValue + valueTypes['f'] = BoolValue + valueTypes['n'] = NilValue + valueTypes['['] = ArrayValue + valueTypes['{'] = ObjectValue +} + +// Iterator is a io.Reader like object, with JSON specific read functions. +// Error is not returned as return value, but stored as Error member on this iterator instance. +type Iterator struct { + cfg *frozenConfig + reader io.Reader + buf []byte + head int + tail int + depth int + captureStartedAt int + captured []byte + Error error + Attachment interface{} // open for customized decoder +} + +// NewIterator creates an empty Iterator instance +func NewIterator(cfg API) *Iterator { + return &Iterator{ + cfg: cfg.(*frozenConfig), + reader: nil, + buf: nil, + head: 0, + tail: 0, + depth: 0, + } +} + +// Parse creates an Iterator instance from io.Reader +func Parse(cfg API, reader io.Reader, bufSize int) *Iterator { + return &Iterator{ + cfg: cfg.(*frozenConfig), + reader: reader, + buf: make([]byte, bufSize), + head: 0, + tail: 0, + depth: 0, + } +} + +// ParseBytes creates an Iterator instance from byte array +func ParseBytes(cfg API, input []byte) *Iterator { + return &Iterator{ + cfg: cfg.(*frozenConfig), + reader: nil, + buf: input, + head: 0, + tail: len(input), + depth: 0, + } +} + +// ParseString creates an Iterator instance from string +func ParseString(cfg API, input string) *Iterator { + return ParseBytes(cfg, []byte(input)) +} + +// Pool returns a pool can provide more iterator with same configuration +func (iter *Iterator) Pool() IteratorPool { + return iter.cfg +} + +// Reset reuse iterator instance by specifying another reader +func (iter *Iterator) Reset(reader io.Reader) *Iterator { + iter.reader = reader + iter.head = 0 + iter.tail = 0 + iter.depth = 0 + return iter +} + +// ResetBytes reuse iterator instance by specifying another byte array as input +func (iter *Iterator) ResetBytes(input []byte) *Iterator { + iter.reader = nil + iter.buf = input + iter.head = 0 + iter.tail = len(input) + iter.depth = 0 + return iter +} + +// WhatIsNext gets ValueType of relatively next json element +func (iter *Iterator) WhatIsNext() ValueType { + valueType := valueTypes[iter.nextToken()] + iter.unreadByte() + return valueType +} + +func (iter *Iterator) skipWhitespacesWithoutLoadMore() bool { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + switch c { + case ' ', '\n', '\t', '\r': + continue + } + iter.head = i + return false + } + return true +} + +func (iter *Iterator) isObjectEnd() bool { + c := iter.nextToken() + if c == ',' { + return false + } + if c == '}' { + return true + } + iter.ReportError("isObjectEnd", "object ended prematurely, unexpected char "+string([]byte{c})) + return true +} + +func (iter *Iterator) nextToken() byte { + // a variation of skip whitespaces, returning the next non-whitespace token + for { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + switch c { + case ' ', '\n', '\t', '\r': + continue + } + iter.head = i + 1 + return c + } + if !iter.loadMore() { + return 0 + } + } +} + +// ReportError record a error in iterator instance with current position. +func (iter *Iterator) ReportError(operation string, msg string) { + if iter.Error != nil { + if iter.Error != io.EOF { + return + } + } + peekStart := iter.head - 10 + if peekStart < 0 { + peekStart = 0 + } + peekEnd := iter.head + 10 + if peekEnd > iter.tail { + peekEnd = iter.tail + } + parsing := string(iter.buf[peekStart:peekEnd]) + contextStart := iter.head - 50 + if contextStart < 0 { + contextStart = 0 + } + contextEnd := iter.head + 50 + if contextEnd > iter.tail { + contextEnd = iter.tail + } + context := string(iter.buf[contextStart:contextEnd]) + iter.Error = fmt.Errorf("%s: %s, error found in #%v byte of ...|%s|..., bigger context ...|%s|...", + operation, msg, iter.head-peekStart, parsing, context) +} + +// CurrentBuffer gets current buffer as string for debugging purpose +func (iter *Iterator) CurrentBuffer() string { + peekStart := iter.head - 10 + if peekStart < 0 { + peekStart = 0 + } + return fmt.Sprintf("parsing #%v byte, around ...|%s|..., whole buffer ...|%s|...", iter.head, + string(iter.buf[peekStart:iter.head]), string(iter.buf[0:iter.tail])) +} + +func (iter *Iterator) readByte() (ret byte) { + if iter.head == iter.tail { + if iter.loadMore() { + ret = iter.buf[iter.head] + iter.head++ + return ret + } + return 0 + } + ret = iter.buf[iter.head] + iter.head++ + return ret +} + +func (iter *Iterator) loadMore() bool { + if iter.reader == nil { + if iter.Error == nil { + iter.head = iter.tail + iter.Error = io.EOF + } + return false + } + if iter.captured != nil { + iter.captured = append(iter.captured, + iter.buf[iter.captureStartedAt:iter.tail]...) + iter.captureStartedAt = 0 + } + for { + n, err := iter.reader.Read(iter.buf) + if n == 0 { + if err != nil { + if iter.Error == nil { + iter.Error = err + } + return false + } + } else { + iter.head = 0 + iter.tail = n + return true + } + } +} + +func (iter *Iterator) unreadByte() { + if iter.Error != nil { + return + } + iter.head-- + return +} + +// Read read the next JSON element as generic interface{}. +func (iter *Iterator) Read() interface{} { + valueType := iter.WhatIsNext() + switch valueType { + case StringValue: + return iter.ReadString() + case NumberValue: + if iter.cfg.configBeforeFrozen.UseNumber { + return json.Number(iter.readNumberAsString()) + } + return iter.ReadFloat64() + case NilValue: + iter.skipFourBytes('n', 'u', 'l', 'l') + return nil + case BoolValue: + return iter.ReadBool() + case ArrayValue: + arr := []interface{}{} + iter.ReadArrayCB(func(iter *Iterator) bool { + var elem interface{} + iter.ReadVal(&elem) + arr = append(arr, elem) + return true + }) + return arr + case ObjectValue: + obj := map[string]interface{}{} + iter.ReadMapCB(func(Iter *Iterator, field string) bool { + var elem interface{} + iter.ReadVal(&elem) + obj[field] = elem + return true + }) + return obj + default: + iter.ReportError("Read", fmt.Sprintf("unexpected value type: %v", valueType)) + return nil + } +} + +// limit maximum depth of nesting, as allowed by https://tools.ietf.org/html/rfc7159#section-9 +const maxDepth = 10000 + +func (iter *Iterator) incrementDepth() (success bool) { + iter.depth++ + if iter.depth <= maxDepth { + return true + } + iter.ReportError("incrementDepth", "exceeded max depth") + return false +} + +func (iter *Iterator) decrementDepth() (success bool) { + iter.depth-- + if iter.depth >= 0 { + return true + } + iter.ReportError("decrementDepth", "unexpected negative nesting") + return false +} diff --git a/vendor/github.com/json-iterator/go/iter_array.go b/vendor/github.com/json-iterator/go/iter_array.go new file mode 100644 index 00000000..204fe0e0 --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter_array.go @@ -0,0 +1,64 @@ +package jsoniter + +// ReadArray read array element, tells if the array has more element to read. +func (iter *Iterator) ReadArray() (ret bool) { + c := iter.nextToken() + switch c { + case 'n': + iter.skipThreeBytes('u', 'l', 'l') + return false // null + case '[': + c = iter.nextToken() + if c != ']' { + iter.unreadByte() + return true + } + return false + case ']': + return false + case ',': + return true + default: + iter.ReportError("ReadArray", "expect [ or , or ] or n, but found "+string([]byte{c})) + return + } +} + +// ReadArrayCB read array with callback +func (iter *Iterator) ReadArrayCB(callback func(*Iterator) bool) (ret bool) { + c := iter.nextToken() + if c == '[' { + if !iter.incrementDepth() { + return false + } + c = iter.nextToken() + if c != ']' { + iter.unreadByte() + if !callback(iter) { + iter.decrementDepth() + return false + } + c = iter.nextToken() + for c == ',' { + if !callback(iter) { + iter.decrementDepth() + return false + } + c = iter.nextToken() + } + if c != ']' { + iter.ReportError("ReadArrayCB", "expect ] in the end, but found "+string([]byte{c})) + iter.decrementDepth() + return false + } + return iter.decrementDepth() + } + return iter.decrementDepth() + } + if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') + return true // null + } + iter.ReportError("ReadArrayCB", "expect [ or n, but found "+string([]byte{c})) + return false +} diff --git a/vendor/github.com/json-iterator/go/iter_float.go b/vendor/github.com/json-iterator/go/iter_float.go new file mode 100644 index 00000000..8a3d8b6f --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter_float.go @@ -0,0 +1,342 @@ +package jsoniter + +import ( + "encoding/json" + "io" + "math/big" + "strconv" + "strings" + "unsafe" +) + +var floatDigits []int8 + +const invalidCharForNumber = int8(-1) +const endOfNumber = int8(-2) +const dotInNumber = int8(-3) + +func init() { + floatDigits = make([]int8, 256) + for i := 0; i < len(floatDigits); i++ { + floatDigits[i] = invalidCharForNumber + } + for i := int8('0'); i <= int8('9'); i++ { + floatDigits[i] = i - int8('0') + } + floatDigits[','] = endOfNumber + floatDigits[']'] = endOfNumber + floatDigits['}'] = endOfNumber + floatDigits[' '] = endOfNumber + floatDigits['\t'] = endOfNumber + floatDigits['\n'] = endOfNumber + floatDigits['.'] = dotInNumber +} + +// ReadBigFloat read big.Float +func (iter *Iterator) ReadBigFloat() (ret *big.Float) { + str := iter.readNumberAsString() + if iter.Error != nil && iter.Error != io.EOF { + return nil + } + prec := 64 + if len(str) > prec { + prec = len(str) + } + val, _, err := big.ParseFloat(str, 10, uint(prec), big.ToZero) + if err != nil { + iter.Error = err + return nil + } + return val +} + +// ReadBigInt read big.Int +func (iter *Iterator) ReadBigInt() (ret *big.Int) { + str := iter.readNumberAsString() + if iter.Error != nil && iter.Error != io.EOF { + return nil + } + ret = big.NewInt(0) + var success bool + ret, success = ret.SetString(str, 10) + if !success { + iter.ReportError("ReadBigInt", "invalid big int") + return nil + } + return ret +} + +//ReadFloat32 read float32 +func (iter *Iterator) ReadFloat32() (ret float32) { + c := iter.nextToken() + if c == '-' { + return -iter.readPositiveFloat32() + } + iter.unreadByte() + return iter.readPositiveFloat32() +} + +func (iter *Iterator) readPositiveFloat32() (ret float32) { + i := iter.head + // first char + if i == iter.tail { + return iter.readFloat32SlowPath() + } + c := iter.buf[i] + i++ + ind := floatDigits[c] + switch ind { + case invalidCharForNumber: + return iter.readFloat32SlowPath() + case endOfNumber: + iter.ReportError("readFloat32", "empty number") + return + case dotInNumber: + iter.ReportError("readFloat32", "leading dot is invalid") + return + case 0: + if i == iter.tail { + return iter.readFloat32SlowPath() + } + c = iter.buf[i] + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + iter.ReportError("readFloat32", "leading zero is invalid") + return + } + } + value := uint64(ind) + // chars before dot +non_decimal_loop: + for ; i < iter.tail; i++ { + c = iter.buf[i] + ind := floatDigits[c] + switch ind { + case invalidCharForNumber: + return iter.readFloat32SlowPath() + case endOfNumber: + iter.head = i + return float32(value) + case dotInNumber: + break non_decimal_loop + } + if value > uint64SafeToMultiple10 { + return iter.readFloat32SlowPath() + } + value = (value << 3) + (value << 1) + uint64(ind) // value = value * 10 + ind; + } + // chars after dot + if c == '.' { + i++ + decimalPlaces := 0 + if i == iter.tail { + return iter.readFloat32SlowPath() + } + for ; i < iter.tail; i++ { + c = iter.buf[i] + ind := floatDigits[c] + switch ind { + case endOfNumber: + if decimalPlaces > 0 && decimalPlaces < len(pow10) { + iter.head = i + return float32(float64(value) / float64(pow10[decimalPlaces])) + } + // too many decimal places + return iter.readFloat32SlowPath() + case invalidCharForNumber, dotInNumber: + return iter.readFloat32SlowPath() + } + decimalPlaces++ + if value > uint64SafeToMultiple10 { + return iter.readFloat32SlowPath() + } + value = (value << 3) + (value << 1) + uint64(ind) + } + } + return iter.readFloat32SlowPath() +} + +func (iter *Iterator) readNumberAsString() (ret string) { + strBuf := [16]byte{} + str := strBuf[0:0] +load_loop: + for { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + switch c { + case '+', '-', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + str = append(str, c) + continue + default: + iter.head = i + break load_loop + } + } + if !iter.loadMore() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF { + return + } + if len(str) == 0 { + iter.ReportError("readNumberAsString", "invalid number") + } + return *(*string)(unsafe.Pointer(&str)) +} + +func (iter *Iterator) readFloat32SlowPath() (ret float32) { + str := iter.readNumberAsString() + if iter.Error != nil && iter.Error != io.EOF { + return + } + errMsg := validateFloat(str) + if errMsg != "" { + iter.ReportError("readFloat32SlowPath", errMsg) + return + } + val, err := strconv.ParseFloat(str, 32) + if err != nil { + iter.Error = err + return + } + return float32(val) +} + +// ReadFloat64 read float64 +func (iter *Iterator) ReadFloat64() (ret float64) { + c := iter.nextToken() + if c == '-' { + return -iter.readPositiveFloat64() + } + iter.unreadByte() + return iter.readPositiveFloat64() +} + +func (iter *Iterator) readPositiveFloat64() (ret float64) { + i := iter.head + // first char + if i == iter.tail { + return iter.readFloat64SlowPath() + } + c := iter.buf[i] + i++ + ind := floatDigits[c] + switch ind { + case invalidCharForNumber: + return iter.readFloat64SlowPath() + case endOfNumber: + iter.ReportError("readFloat64", "empty number") + return + case dotInNumber: + iter.ReportError("readFloat64", "leading dot is invalid") + return + case 0: + if i == iter.tail { + return iter.readFloat64SlowPath() + } + c = iter.buf[i] + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + iter.ReportError("readFloat64", "leading zero is invalid") + return + } + } + value := uint64(ind) + // chars before dot +non_decimal_loop: + for ; i < iter.tail; i++ { + c = iter.buf[i] + ind := floatDigits[c] + switch ind { + case invalidCharForNumber: + return iter.readFloat64SlowPath() + case endOfNumber: + iter.head = i + return float64(value) + case dotInNumber: + break non_decimal_loop + } + if value > uint64SafeToMultiple10 { + return iter.readFloat64SlowPath() + } + value = (value << 3) + (value << 1) + uint64(ind) // value = value * 10 + ind; + } + // chars after dot + if c == '.' { + i++ + decimalPlaces := 0 + if i == iter.tail { + return iter.readFloat64SlowPath() + } + for ; i < iter.tail; i++ { + c = iter.buf[i] + ind := floatDigits[c] + switch ind { + case endOfNumber: + if decimalPlaces > 0 && decimalPlaces < len(pow10) { + iter.head = i + return float64(value) / float64(pow10[decimalPlaces]) + } + // too many decimal places + return iter.readFloat64SlowPath() + case invalidCharForNumber, dotInNumber: + return iter.readFloat64SlowPath() + } + decimalPlaces++ + if value > uint64SafeToMultiple10 { + return iter.readFloat64SlowPath() + } + value = (value << 3) + (value << 1) + uint64(ind) + if value > maxFloat64 { + return iter.readFloat64SlowPath() + } + } + } + return iter.readFloat64SlowPath() +} + +func (iter *Iterator) readFloat64SlowPath() (ret float64) { + str := iter.readNumberAsString() + if iter.Error != nil && iter.Error != io.EOF { + return + } + errMsg := validateFloat(str) + if errMsg != "" { + iter.ReportError("readFloat64SlowPath", errMsg) + return + } + val, err := strconv.ParseFloat(str, 64) + if err != nil { + iter.Error = err + return + } + return val +} + +func validateFloat(str string) string { + // strconv.ParseFloat is not validating `1.` or `1.e1` + if len(str) == 0 { + return "empty number" + } + if str[0] == '-' { + return "-- is not valid" + } + dotPos := strings.IndexByte(str, '.') + if dotPos != -1 { + if dotPos == len(str)-1 { + return "dot can not be last character" + } + switch str[dotPos+1] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + return "missing digit after dot" + } + } + return "" +} + +// ReadNumber read json.Number +func (iter *Iterator) ReadNumber() (ret json.Number) { + return json.Number(iter.readNumberAsString()) +} diff --git a/vendor/github.com/json-iterator/go/iter_int.go b/vendor/github.com/json-iterator/go/iter_int.go new file mode 100644 index 00000000..d786a89f --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter_int.go @@ -0,0 +1,346 @@ +package jsoniter + +import ( + "math" + "strconv" +) + +var intDigits []int8 + +const uint32SafeToMultiply10 = uint32(0xffffffff)/10 - 1 +const uint64SafeToMultiple10 = uint64(0xffffffffffffffff)/10 - 1 +const maxFloat64 = 1<<53 - 1 + +func init() { + intDigits = make([]int8, 256) + for i := 0; i < len(intDigits); i++ { + intDigits[i] = invalidCharForNumber + } + for i := int8('0'); i <= int8('9'); i++ { + intDigits[i] = i - int8('0') + } +} + +// ReadUint read uint +func (iter *Iterator) ReadUint() uint { + if strconv.IntSize == 32 { + return uint(iter.ReadUint32()) + } + return uint(iter.ReadUint64()) +} + +// ReadInt read int +func (iter *Iterator) ReadInt() int { + if strconv.IntSize == 32 { + return int(iter.ReadInt32()) + } + return int(iter.ReadInt64()) +} + +// ReadInt8 read int8 +func (iter *Iterator) ReadInt8() (ret int8) { + c := iter.nextToken() + if c == '-' { + val := iter.readUint32(iter.readByte()) + if val > math.MaxInt8+1 { + iter.ReportError("ReadInt8", "overflow: "+strconv.FormatInt(int64(val), 10)) + return + } + return -int8(val) + } + val := iter.readUint32(c) + if val > math.MaxInt8 { + iter.ReportError("ReadInt8", "overflow: "+strconv.FormatInt(int64(val), 10)) + return + } + return int8(val) +} + +// ReadUint8 read uint8 +func (iter *Iterator) ReadUint8() (ret uint8) { + val := iter.readUint32(iter.nextToken()) + if val > math.MaxUint8 { + iter.ReportError("ReadUint8", "overflow: "+strconv.FormatInt(int64(val), 10)) + return + } + return uint8(val) +} + +// ReadInt16 read int16 +func (iter *Iterator) ReadInt16() (ret int16) { + c := iter.nextToken() + if c == '-' { + val := iter.readUint32(iter.readByte()) + if val > math.MaxInt16+1 { + iter.ReportError("ReadInt16", "overflow: "+strconv.FormatInt(int64(val), 10)) + return + } + return -int16(val) + } + val := iter.readUint32(c) + if val > math.MaxInt16 { + iter.ReportError("ReadInt16", "overflow: "+strconv.FormatInt(int64(val), 10)) + return + } + return int16(val) +} + +// ReadUint16 read uint16 +func (iter *Iterator) ReadUint16() (ret uint16) { + val := iter.readUint32(iter.nextToken()) + if val > math.MaxUint16 { + iter.ReportError("ReadUint16", "overflow: "+strconv.FormatInt(int64(val), 10)) + return + } + return uint16(val) +} + +// ReadInt32 read int32 +func (iter *Iterator) ReadInt32() (ret int32) { + c := iter.nextToken() + if c == '-' { + val := iter.readUint32(iter.readByte()) + if val > math.MaxInt32+1 { + iter.ReportError("ReadInt32", "overflow: "+strconv.FormatInt(int64(val), 10)) + return + } + return -int32(val) + } + val := iter.readUint32(c) + if val > math.MaxInt32 { + iter.ReportError("ReadInt32", "overflow: "+strconv.FormatInt(int64(val), 10)) + return + } + return int32(val) +} + +// ReadUint32 read uint32 +func (iter *Iterator) ReadUint32() (ret uint32) { + return iter.readUint32(iter.nextToken()) +} + +func (iter *Iterator) readUint32(c byte) (ret uint32) { + ind := intDigits[c] + if ind == 0 { + iter.assertInteger() + return 0 // single zero + } + if ind == invalidCharForNumber { + iter.ReportError("readUint32", "unexpected character: "+string([]byte{byte(ind)})) + return + } + value := uint32(ind) + if iter.tail-iter.head > 10 { + i := iter.head + ind2 := intDigits[iter.buf[i]] + if ind2 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value + } + i++ + ind3 := intDigits[iter.buf[i]] + if ind3 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*10 + uint32(ind2) + } + //iter.head = i + 1 + //value = value * 100 + uint32(ind2) * 10 + uint32(ind3) + i++ + ind4 := intDigits[iter.buf[i]] + if ind4 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*100 + uint32(ind2)*10 + uint32(ind3) + } + i++ + ind5 := intDigits[iter.buf[i]] + if ind5 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*1000 + uint32(ind2)*100 + uint32(ind3)*10 + uint32(ind4) + } + i++ + ind6 := intDigits[iter.buf[i]] + if ind6 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*10000 + uint32(ind2)*1000 + uint32(ind3)*100 + uint32(ind4)*10 + uint32(ind5) + } + i++ + ind7 := intDigits[iter.buf[i]] + if ind7 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*100000 + uint32(ind2)*10000 + uint32(ind3)*1000 + uint32(ind4)*100 + uint32(ind5)*10 + uint32(ind6) + } + i++ + ind8 := intDigits[iter.buf[i]] + if ind8 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*1000000 + uint32(ind2)*100000 + uint32(ind3)*10000 + uint32(ind4)*1000 + uint32(ind5)*100 + uint32(ind6)*10 + uint32(ind7) + } + i++ + ind9 := intDigits[iter.buf[i]] + value = value*10000000 + uint32(ind2)*1000000 + uint32(ind3)*100000 + uint32(ind4)*10000 + uint32(ind5)*1000 + uint32(ind6)*100 + uint32(ind7)*10 + uint32(ind8) + iter.head = i + if ind9 == invalidCharForNumber { + iter.assertInteger() + return value + } + } + for { + for i := iter.head; i < iter.tail; i++ { + ind = intDigits[iter.buf[i]] + if ind == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value + } + if value > uint32SafeToMultiply10 { + value2 := (value << 3) + (value << 1) + uint32(ind) + if value2 < value { + iter.ReportError("readUint32", "overflow") + return + } + value = value2 + continue + } + value = (value << 3) + (value << 1) + uint32(ind) + } + if !iter.loadMore() { + iter.assertInteger() + return value + } + } +} + +// ReadInt64 read int64 +func (iter *Iterator) ReadInt64() (ret int64) { + c := iter.nextToken() + if c == '-' { + val := iter.readUint64(iter.readByte()) + if val > math.MaxInt64+1 { + iter.ReportError("ReadInt64", "overflow: "+strconv.FormatUint(uint64(val), 10)) + return + } + return -int64(val) + } + val := iter.readUint64(c) + if val > math.MaxInt64 { + iter.ReportError("ReadInt64", "overflow: "+strconv.FormatUint(uint64(val), 10)) + return + } + return int64(val) +} + +// ReadUint64 read uint64 +func (iter *Iterator) ReadUint64() uint64 { + return iter.readUint64(iter.nextToken()) +} + +func (iter *Iterator) readUint64(c byte) (ret uint64) { + ind := intDigits[c] + if ind == 0 { + iter.assertInteger() + return 0 // single zero + } + if ind == invalidCharForNumber { + iter.ReportError("readUint64", "unexpected character: "+string([]byte{byte(ind)})) + return + } + value := uint64(ind) + if iter.tail-iter.head > 10 { + i := iter.head + ind2 := intDigits[iter.buf[i]] + if ind2 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value + } + i++ + ind3 := intDigits[iter.buf[i]] + if ind3 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*10 + uint64(ind2) + } + //iter.head = i + 1 + //value = value * 100 + uint32(ind2) * 10 + uint32(ind3) + i++ + ind4 := intDigits[iter.buf[i]] + if ind4 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*100 + uint64(ind2)*10 + uint64(ind3) + } + i++ + ind5 := intDigits[iter.buf[i]] + if ind5 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*1000 + uint64(ind2)*100 + uint64(ind3)*10 + uint64(ind4) + } + i++ + ind6 := intDigits[iter.buf[i]] + if ind6 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*10000 + uint64(ind2)*1000 + uint64(ind3)*100 + uint64(ind4)*10 + uint64(ind5) + } + i++ + ind7 := intDigits[iter.buf[i]] + if ind7 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*100000 + uint64(ind2)*10000 + uint64(ind3)*1000 + uint64(ind4)*100 + uint64(ind5)*10 + uint64(ind6) + } + i++ + ind8 := intDigits[iter.buf[i]] + if ind8 == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value*1000000 + uint64(ind2)*100000 + uint64(ind3)*10000 + uint64(ind4)*1000 + uint64(ind5)*100 + uint64(ind6)*10 + uint64(ind7) + } + i++ + ind9 := intDigits[iter.buf[i]] + value = value*10000000 + uint64(ind2)*1000000 + uint64(ind3)*100000 + uint64(ind4)*10000 + uint64(ind5)*1000 + uint64(ind6)*100 + uint64(ind7)*10 + uint64(ind8) + iter.head = i + if ind9 == invalidCharForNumber { + iter.assertInteger() + return value + } + } + for { + for i := iter.head; i < iter.tail; i++ { + ind = intDigits[iter.buf[i]] + if ind == invalidCharForNumber { + iter.head = i + iter.assertInteger() + return value + } + if value > uint64SafeToMultiple10 { + value2 := (value << 3) + (value << 1) + uint64(ind) + if value2 < value { + iter.ReportError("readUint64", "overflow") + return + } + value = value2 + continue + } + value = (value << 3) + (value << 1) + uint64(ind) + } + if !iter.loadMore() { + iter.assertInteger() + return value + } + } +} + +func (iter *Iterator) assertInteger() { + if iter.head < iter.tail && iter.buf[iter.head] == '.' { + iter.ReportError("assertInteger", "can not decode float as int") + } +} diff --git a/vendor/github.com/json-iterator/go/iter_object.go b/vendor/github.com/json-iterator/go/iter_object.go new file mode 100644 index 00000000..58ee89c8 --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter_object.go @@ -0,0 +1,267 @@ +package jsoniter + +import ( + "fmt" + "strings" +) + +// ReadObject read one field from object. +// If object ended, returns empty string. +// Otherwise, returns the field name. +func (iter *Iterator) ReadObject() (ret string) { + c := iter.nextToken() + switch c { + case 'n': + iter.skipThreeBytes('u', 'l', 'l') + return "" // null + case '{': + c = iter.nextToken() + if c == '"' { + iter.unreadByte() + field := iter.ReadString() + c = iter.nextToken() + if c != ':' { + iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c})) + } + return field + } + if c == '}' { + return "" // end of object + } + iter.ReportError("ReadObject", `expect " after {, but found `+string([]byte{c})) + return + case ',': + field := iter.ReadString() + c = iter.nextToken() + if c != ':' { + iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c})) + } + return field + case '}': + return "" // end of object + default: + iter.ReportError("ReadObject", fmt.Sprintf(`expect { or , or } or n, but found %s`, string([]byte{c}))) + return + } +} + +// CaseInsensitive +func (iter *Iterator) readFieldHash() int64 { + hash := int64(0x811c9dc5) + c := iter.nextToken() + if c != '"' { + iter.ReportError("readFieldHash", `expect ", but found `+string([]byte{c})) + return 0 + } + for { + for i := iter.head; i < iter.tail; i++ { + // require ascii string and no escape + b := iter.buf[i] + if b == '\\' { + iter.head = i + for _, b := range iter.readStringSlowPath() { + if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive { + b += 'a' - 'A' + } + hash ^= int64(b) + hash *= 0x1000193 + } + c = iter.nextToken() + if c != ':' { + iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c})) + return 0 + } + return hash + } + if b == '"' { + iter.head = i + 1 + c = iter.nextToken() + if c != ':' { + iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c})) + return 0 + } + return hash + } + if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive { + b += 'a' - 'A' + } + hash ^= int64(b) + hash *= 0x1000193 + } + if !iter.loadMore() { + iter.ReportError("readFieldHash", `incomplete field name`) + return 0 + } + } +} + +func calcHash(str string, caseSensitive bool) int64 { + if !caseSensitive { + str = strings.ToLower(str) + } + hash := int64(0x811c9dc5) + for _, b := range []byte(str) { + hash ^= int64(b) + hash *= 0x1000193 + } + return int64(hash) +} + +// ReadObjectCB read object with callback, the key is ascii only and field name not copied +func (iter *Iterator) ReadObjectCB(callback func(*Iterator, string) bool) bool { + c := iter.nextToken() + var field string + if c == '{' { + if !iter.incrementDepth() { + return false + } + c = iter.nextToken() + if c == '"' { + iter.unreadByte() + field = iter.ReadString() + c = iter.nextToken() + if c != ':' { + iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c})) + } + if !callback(iter, field) { + iter.decrementDepth() + return false + } + c = iter.nextToken() + for c == ',' { + field = iter.ReadString() + c = iter.nextToken() + if c != ':' { + iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c})) + } + if !callback(iter, field) { + iter.decrementDepth() + return false + } + c = iter.nextToken() + } + if c != '}' { + iter.ReportError("ReadObjectCB", `object not ended with }`) + iter.decrementDepth() + return false + } + return iter.decrementDepth() + } + if c == '}' { + return iter.decrementDepth() + } + iter.ReportError("ReadObjectCB", `expect " after {, but found `+string([]byte{c})) + iter.decrementDepth() + return false + } + if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') + return true // null + } + iter.ReportError("ReadObjectCB", `expect { or n, but found `+string([]byte{c})) + return false +} + +// ReadMapCB read map with callback, the key can be any string +func (iter *Iterator) ReadMapCB(callback func(*Iterator, string) bool) bool { + c := iter.nextToken() + if c == '{' { + if !iter.incrementDepth() { + return false + } + c = iter.nextToken() + if c == '"' { + iter.unreadByte() + field := iter.ReadString() + if iter.nextToken() != ':' { + iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c})) + iter.decrementDepth() + return false + } + if !callback(iter, field) { + iter.decrementDepth() + return false + } + c = iter.nextToken() + for c == ',' { + field = iter.ReadString() + if iter.nextToken() != ':' { + iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c})) + iter.decrementDepth() + return false + } + if !callback(iter, field) { + iter.decrementDepth() + return false + } + c = iter.nextToken() + } + if c != '}' { + iter.ReportError("ReadMapCB", `object not ended with }`) + iter.decrementDepth() + return false + } + return iter.decrementDepth() + } + if c == '}' { + return iter.decrementDepth() + } + iter.ReportError("ReadMapCB", `expect " after {, but found `+string([]byte{c})) + iter.decrementDepth() + return false + } + if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') + return true // null + } + iter.ReportError("ReadMapCB", `expect { or n, but found `+string([]byte{c})) + return false +} + +func (iter *Iterator) readObjectStart() bool { + c := iter.nextToken() + if c == '{' { + c = iter.nextToken() + if c == '}' { + return false + } + iter.unreadByte() + return true + } else if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') + return false + } + iter.ReportError("readObjectStart", "expect { or n, but found "+string([]byte{c})) + return false +} + +func (iter *Iterator) readObjectFieldAsBytes() (ret []byte) { + str := iter.ReadStringAsSlice() + if iter.skipWhitespacesWithoutLoadMore() { + if ret == nil { + ret = make([]byte, len(str)) + copy(ret, str) + } + if !iter.loadMore() { + return + } + } + if iter.buf[iter.head] != ':' { + iter.ReportError("readObjectFieldAsBytes", "expect : after object field, but found "+string([]byte{iter.buf[iter.head]})) + return + } + iter.head++ + if iter.skipWhitespacesWithoutLoadMore() { + if ret == nil { + ret = make([]byte, len(str)) + copy(ret, str) + } + if !iter.loadMore() { + return + } + } + if ret == nil { + return str + } + return ret +} diff --git a/vendor/github.com/json-iterator/go/iter_skip.go b/vendor/github.com/json-iterator/go/iter_skip.go new file mode 100644 index 00000000..e91eefb1 --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter_skip.go @@ -0,0 +1,130 @@ +package jsoniter + +import "fmt" + +// ReadNil reads a json object as nil and +// returns whether it's a nil or not +func (iter *Iterator) ReadNil() (ret bool) { + c := iter.nextToken() + if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') // null + return true + } + iter.unreadByte() + return false +} + +// ReadBool reads a json object as BoolValue +func (iter *Iterator) ReadBool() (ret bool) { + c := iter.nextToken() + if c == 't' { + iter.skipThreeBytes('r', 'u', 'e') + return true + } + if c == 'f' { + iter.skipFourBytes('a', 'l', 's', 'e') + return false + } + iter.ReportError("ReadBool", "expect t or f, but found "+string([]byte{c})) + return +} + +// SkipAndReturnBytes skip next JSON element, and return its content as []byte. +// The []byte can be kept, it is a copy of data. +func (iter *Iterator) SkipAndReturnBytes() []byte { + iter.startCapture(iter.head) + iter.Skip() + return iter.stopCapture() +} + +// SkipAndAppendBytes skips next JSON element and appends its content to +// buffer, returning the result. +func (iter *Iterator) SkipAndAppendBytes(buf []byte) []byte { + iter.startCaptureTo(buf, iter.head) + iter.Skip() + return iter.stopCapture() +} + +func (iter *Iterator) startCaptureTo(buf []byte, captureStartedAt int) { + if iter.captured != nil { + panic("already in capture mode") + } + iter.captureStartedAt = captureStartedAt + iter.captured = buf +} + +func (iter *Iterator) startCapture(captureStartedAt int) { + iter.startCaptureTo(make([]byte, 0, 32), captureStartedAt) +} + +func (iter *Iterator) stopCapture() []byte { + if iter.captured == nil { + panic("not in capture mode") + } + captured := iter.captured + remaining := iter.buf[iter.captureStartedAt:iter.head] + iter.captureStartedAt = -1 + iter.captured = nil + return append(captured, remaining...) +} + +// Skip skips a json object and positions to relatively the next json object +func (iter *Iterator) Skip() { + c := iter.nextToken() + switch c { + case '"': + iter.skipString() + case 'n': + iter.skipThreeBytes('u', 'l', 'l') // null + case 't': + iter.skipThreeBytes('r', 'u', 'e') // true + case 'f': + iter.skipFourBytes('a', 'l', 's', 'e') // false + case '0': + iter.unreadByte() + iter.ReadFloat32() + case '-', '1', '2', '3', '4', '5', '6', '7', '8', '9': + iter.skipNumber() + case '[': + iter.skipArray() + case '{': + iter.skipObject() + default: + iter.ReportError("Skip", fmt.Sprintf("do not know how to skip: %v", c)) + return + } +} + +func (iter *Iterator) skipFourBytes(b1, b2, b3, b4 byte) { + if iter.readByte() != b1 { + iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4}))) + return + } + if iter.readByte() != b2 { + iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4}))) + return + } + if iter.readByte() != b3 { + iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4}))) + return + } + if iter.readByte() != b4 { + iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4}))) + return + } +} + +func (iter *Iterator) skipThreeBytes(b1, b2, b3 byte) { + if iter.readByte() != b1 { + iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3}))) + return + } + if iter.readByte() != b2 { + iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3}))) + return + } + if iter.readByte() != b3 { + iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3}))) + return + } +} diff --git a/vendor/github.com/json-iterator/go/iter_skip_sloppy.go b/vendor/github.com/json-iterator/go/iter_skip_sloppy.go new file mode 100644 index 00000000..9303de41 --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter_skip_sloppy.go @@ -0,0 +1,163 @@ +//+build jsoniter_sloppy + +package jsoniter + +// sloppy but faster implementation, do not validate the input json + +func (iter *Iterator) skipNumber() { + for { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + switch c { + case ' ', '\n', '\r', '\t', ',', '}', ']': + iter.head = i + return + } + } + if !iter.loadMore() { + return + } + } +} + +func (iter *Iterator) skipArray() { + level := 1 + if !iter.incrementDepth() { + return + } + for { + for i := iter.head; i < iter.tail; i++ { + switch iter.buf[i] { + case '"': // If inside string, skip it + iter.head = i + 1 + iter.skipString() + i = iter.head - 1 // it will be i++ soon + case '[': // If open symbol, increase level + level++ + if !iter.incrementDepth() { + return + } + case ']': // If close symbol, increase level + level-- + if !iter.decrementDepth() { + return + } + + // If we have returned to the original level, we're done + if level == 0 { + iter.head = i + 1 + return + } + } + } + if !iter.loadMore() { + iter.ReportError("skipObject", "incomplete array") + return + } + } +} + +func (iter *Iterator) skipObject() { + level := 1 + if !iter.incrementDepth() { + return + } + + for { + for i := iter.head; i < iter.tail; i++ { + switch iter.buf[i] { + case '"': // If inside string, skip it + iter.head = i + 1 + iter.skipString() + i = iter.head - 1 // it will be i++ soon + case '{': // If open symbol, increase level + level++ + if !iter.incrementDepth() { + return + } + case '}': // If close symbol, increase level + level-- + if !iter.decrementDepth() { + return + } + + // If we have returned to the original level, we're done + if level == 0 { + iter.head = i + 1 + return + } + } + } + if !iter.loadMore() { + iter.ReportError("skipObject", "incomplete object") + return + } + } +} + +func (iter *Iterator) skipString() { + for { + end, escaped := iter.findStringEnd() + if end == -1 { + if !iter.loadMore() { + iter.ReportError("skipString", "incomplete string") + return + } + if escaped { + iter.head = 1 // skip the first char as last char read is \ + } + } else { + iter.head = end + return + } + } +} + +// adapted from: https://github.com/buger/jsonparser/blob/master/parser.go +// Tries to find the end of string +// Support if string contains escaped quote symbols. +func (iter *Iterator) findStringEnd() (int, bool) { + escaped := false + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + if c == '"' { + if !escaped { + return i + 1, false + } + j := i - 1 + for { + if j < iter.head || iter.buf[j] != '\\' { + // even number of backslashes + // either end of buffer, or " found + return i + 1, true + } + j-- + if j < iter.head || iter.buf[j] != '\\' { + // odd number of backslashes + // it is \" or \\\" + break + } + j-- + } + } else if c == '\\' { + escaped = true + } + } + j := iter.tail - 1 + for { + if j < iter.head || iter.buf[j] != '\\' { + // even number of backslashes + // either end of buffer, or " found + return -1, false // do not end with \ + } + j-- + if j < iter.head || iter.buf[j] != '\\' { + // odd number of backslashes + // it is \" or \\\" + break + } + j-- + + } + return -1, true // end with \ +} diff --git a/vendor/github.com/json-iterator/go/iter_skip_strict.go b/vendor/github.com/json-iterator/go/iter_skip_strict.go new file mode 100644 index 00000000..6cf66d04 --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter_skip_strict.go @@ -0,0 +1,99 @@ +//+build !jsoniter_sloppy + +package jsoniter + +import ( + "fmt" + "io" +) + +func (iter *Iterator) skipNumber() { + if !iter.trySkipNumber() { + iter.unreadByte() + if iter.Error != nil && iter.Error != io.EOF { + return + } + iter.ReadFloat64() + if iter.Error != nil && iter.Error != io.EOF { + iter.Error = nil + iter.ReadBigFloat() + } + } +} + +func (iter *Iterator) trySkipNumber() bool { + dotFound := false + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + case '.': + if dotFound { + iter.ReportError("validateNumber", `more than one dot found in number`) + return true // already failed + } + if i+1 == iter.tail { + return false + } + c = iter.buf[i+1] + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + default: + iter.ReportError("validateNumber", `missing digit after dot`) + return true // already failed + } + dotFound = true + default: + switch c { + case ',', ']', '}', ' ', '\t', '\n', '\r': + if iter.head == i { + return false // if - without following digits + } + iter.head = i + return true // must be valid + } + return false // may be invalid + } + } + return false +} + +func (iter *Iterator) skipString() { + if !iter.trySkipString() { + iter.unreadByte() + iter.ReadString() + } +} + +func (iter *Iterator) trySkipString() bool { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + if c == '"' { + iter.head = i + 1 + return true // valid + } else if c == '\\' { + return false + } else if c < ' ' { + iter.ReportError("trySkipString", + fmt.Sprintf(`invalid control character found: %d`, c)) + return true // already failed + } + } + return false +} + +func (iter *Iterator) skipObject() { + iter.unreadByte() + iter.ReadObjectCB(func(iter *Iterator, field string) bool { + iter.Skip() + return true + }) +} + +func (iter *Iterator) skipArray() { + iter.unreadByte() + iter.ReadArrayCB(func(iter *Iterator) bool { + iter.Skip() + return true + }) +} diff --git a/vendor/github.com/json-iterator/go/iter_str.go b/vendor/github.com/json-iterator/go/iter_str.go new file mode 100644 index 00000000..adc487ea --- /dev/null +++ b/vendor/github.com/json-iterator/go/iter_str.go @@ -0,0 +1,215 @@ +package jsoniter + +import ( + "fmt" + "unicode/utf16" +) + +// ReadString read string from iterator +func (iter *Iterator) ReadString() (ret string) { + c := iter.nextToken() + if c == '"' { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + if c == '"' { + ret = string(iter.buf[iter.head:i]) + iter.head = i + 1 + return ret + } else if c == '\\' { + break + } else if c < ' ' { + iter.ReportError("ReadString", + fmt.Sprintf(`invalid control character found: %d`, c)) + return + } + } + return iter.readStringSlowPath() + } else if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') + return "" + } + iter.ReportError("ReadString", `expects " or n, but found `+string([]byte{c})) + return +} + +func (iter *Iterator) readStringSlowPath() (ret string) { + var str []byte + var c byte + for iter.Error == nil { + c = iter.readByte() + if c == '"' { + return string(str) + } + if c == '\\' { + c = iter.readByte() + str = iter.readEscapedChar(c, str) + } else { + str = append(str, c) + } + } + iter.ReportError("readStringSlowPath", "unexpected end of input") + return +} + +func (iter *Iterator) readEscapedChar(c byte, str []byte) []byte { + switch c { + case 'u': + r := iter.readU4() + if utf16.IsSurrogate(r) { + c = iter.readByte() + if iter.Error != nil { + return nil + } + if c != '\\' { + iter.unreadByte() + str = appendRune(str, r) + return str + } + c = iter.readByte() + if iter.Error != nil { + return nil + } + if c != 'u' { + str = appendRune(str, r) + return iter.readEscapedChar(c, str) + } + r2 := iter.readU4() + if iter.Error != nil { + return nil + } + combined := utf16.DecodeRune(r, r2) + if combined == '\uFFFD' { + str = appendRune(str, r) + str = appendRune(str, r2) + } else { + str = appendRune(str, combined) + } + } else { + str = appendRune(str, r) + } + case '"': + str = append(str, '"') + case '\\': + str = append(str, '\\') + case '/': + str = append(str, '/') + case 'b': + str = append(str, '\b') + case 'f': + str = append(str, '\f') + case 'n': + str = append(str, '\n') + case 'r': + str = append(str, '\r') + case 't': + str = append(str, '\t') + default: + iter.ReportError("readEscapedChar", + `invalid escape char after \`) + return nil + } + return str +} + +// ReadStringAsSlice read string from iterator without copying into string form. +// The []byte can not be kept, as it will change after next iterator call. +func (iter *Iterator) ReadStringAsSlice() (ret []byte) { + c := iter.nextToken() + if c == '"' { + for i := iter.head; i < iter.tail; i++ { + // require ascii string and no escape + // for: field name, base64, number + if iter.buf[i] == '"' { + // fast path: reuse the underlying buffer + ret = iter.buf[iter.head:i] + iter.head = i + 1 + return ret + } + } + readLen := iter.tail - iter.head + copied := make([]byte, readLen, readLen*2) + copy(copied, iter.buf[iter.head:iter.tail]) + iter.head = iter.tail + for iter.Error == nil { + c := iter.readByte() + if c == '"' { + return copied + } + copied = append(copied, c) + } + return copied + } + iter.ReportError("ReadStringAsSlice", `expects " or n, but found `+string([]byte{c})) + return +} + +func (iter *Iterator) readU4() (ret rune) { + for i := 0; i < 4; i++ { + c := iter.readByte() + if iter.Error != nil { + return + } + if c >= '0' && c <= '9' { + ret = ret*16 + rune(c-'0') + } else if c >= 'a' && c <= 'f' { + ret = ret*16 + rune(c-'a'+10) + } else if c >= 'A' && c <= 'F' { + ret = ret*16 + rune(c-'A'+10) + } else { + iter.ReportError("readU4", "expects 0~9 or a~f, but found "+string([]byte{c})) + return + } + } + return ret +} + +const ( + t1 = 0x00 // 0000 0000 + tx = 0x80 // 1000 0000 + t2 = 0xC0 // 1100 0000 + t3 = 0xE0 // 1110 0000 + t4 = 0xF0 // 1111 0000 + t5 = 0xF8 // 1111 1000 + + maskx = 0x3F // 0011 1111 + mask2 = 0x1F // 0001 1111 + mask3 = 0x0F // 0000 1111 + mask4 = 0x07 // 0000 0111 + + rune1Max = 1<<7 - 1 + rune2Max = 1<<11 - 1 + rune3Max = 1<<16 - 1 + + surrogateMin = 0xD800 + surrogateMax = 0xDFFF + + maxRune = '\U0010FFFF' // Maximum valid Unicode code point. + runeError = '\uFFFD' // the "error" Rune or "Unicode replacement character" +) + +func appendRune(p []byte, r rune) []byte { + // Negative values are erroneous. Making it unsigned addresses the problem. + switch i := uint32(r); { + case i <= rune1Max: + p = append(p, byte(r)) + return p + case i <= rune2Max: + p = append(p, t2|byte(r>>6)) + p = append(p, tx|byte(r)&maskx) + return p + case i > maxRune, surrogateMin <= i && i <= surrogateMax: + r = runeError + fallthrough + case i <= rune3Max: + p = append(p, t3|byte(r>>12)) + p = append(p, tx|byte(r>>6)&maskx) + p = append(p, tx|byte(r)&maskx) + return p + default: + p = append(p, t4|byte(r>>18)) + p = append(p, tx|byte(r>>12)&maskx) + p = append(p, tx|byte(r>>6)&maskx) + p = append(p, tx|byte(r)&maskx) + return p + } +} diff --git a/vendor/github.com/json-iterator/go/jsoniter.go b/vendor/github.com/json-iterator/go/jsoniter.go new file mode 100644 index 00000000..c2934f91 --- /dev/null +++ b/vendor/github.com/json-iterator/go/jsoniter.go @@ -0,0 +1,18 @@ +// Package jsoniter implements encoding and decoding of JSON as defined in +// RFC 4627 and provides interfaces with identical syntax of standard lib encoding/json. +// Converting from encoding/json to jsoniter is no more than replacing the package with jsoniter +// and variable type declarations (if any). +// jsoniter interfaces gives 100% compatibility with code using standard lib. +// +// "JSON and Go" +// (https://golang.org/doc/articles/json_and_go.html) +// gives a description of how Marshal/Unmarshal operate +// between arbitrary or predefined json objects and bytes, +// and it applies to jsoniter.Marshal/Unmarshal as well. +// +// Besides, jsoniter.Iterator provides a different set of interfaces +// iterating given bytes/string/reader +// and yielding parsed elements one by one. +// This set of interfaces reads input as required and gives +// better performance. +package jsoniter diff --git a/vendor/github.com/json-iterator/go/pool.go b/vendor/github.com/json-iterator/go/pool.go new file mode 100644 index 00000000..e2389b56 --- /dev/null +++ b/vendor/github.com/json-iterator/go/pool.go @@ -0,0 +1,42 @@ +package jsoniter + +import ( + "io" +) + +// IteratorPool a thread safe pool of iterators with same configuration +type IteratorPool interface { + BorrowIterator(data []byte) *Iterator + ReturnIterator(iter *Iterator) +} + +// StreamPool a thread safe pool of streams with same configuration +type StreamPool interface { + BorrowStream(writer io.Writer) *Stream + ReturnStream(stream *Stream) +} + +func (cfg *frozenConfig) BorrowStream(writer io.Writer) *Stream { + stream := cfg.streamPool.Get().(*Stream) + stream.Reset(writer) + return stream +} + +func (cfg *frozenConfig) ReturnStream(stream *Stream) { + stream.out = nil + stream.Error = nil + stream.Attachment = nil + cfg.streamPool.Put(stream) +} + +func (cfg *frozenConfig) BorrowIterator(data []byte) *Iterator { + iter := cfg.iteratorPool.Get().(*Iterator) + iter.ResetBytes(data) + return iter +} + +func (cfg *frozenConfig) ReturnIterator(iter *Iterator) { + iter.Error = nil + iter.Attachment = nil + cfg.iteratorPool.Put(iter) +} diff --git a/vendor/github.com/json-iterator/go/reflect.go b/vendor/github.com/json-iterator/go/reflect.go new file mode 100644 index 00000000..39acb320 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect.go @@ -0,0 +1,337 @@ +package jsoniter + +import ( + "fmt" + "reflect" + "unsafe" + + "github.com/modern-go/reflect2" +) + +// ValDecoder is an internal type registered to cache as needed. +// Don't confuse jsoniter.ValDecoder with json.Decoder. +// For json.Decoder's adapter, refer to jsoniter.AdapterDecoder(todo link). +// +// Reflection on type to create decoders, which is then cached +// Reflection on value is avoided as we can, as the reflect.Value itself will allocate, with following exceptions +// 1. create instance of new value, for example *int will need a int to be allocated +// 2. append to slice, if the existing cap is not enough, allocate will be done using Reflect.New +// 3. assignment to map, both key and value will be reflect.Value +// For a simple struct binding, it will be reflect.Value free and allocation free +type ValDecoder interface { + Decode(ptr unsafe.Pointer, iter *Iterator) +} + +// ValEncoder is an internal type registered to cache as needed. +// Don't confuse jsoniter.ValEncoder with json.Encoder. +// For json.Encoder's adapter, refer to jsoniter.AdapterEncoder(todo godoc link). +type ValEncoder interface { + IsEmpty(ptr unsafe.Pointer) bool + Encode(ptr unsafe.Pointer, stream *Stream) +} + +type checkIsEmpty interface { + IsEmpty(ptr unsafe.Pointer) bool +} + +type ctx struct { + *frozenConfig + prefix string + encoders map[reflect2.Type]ValEncoder + decoders map[reflect2.Type]ValDecoder +} + +func (b *ctx) caseSensitive() bool { + if b.frozenConfig == nil { + // default is case-insensitive + return false + } + return b.frozenConfig.caseSensitive +} + +func (b *ctx) append(prefix string) *ctx { + return &ctx{ + frozenConfig: b.frozenConfig, + prefix: b.prefix + " " + prefix, + encoders: b.encoders, + decoders: b.decoders, + } +} + +// ReadVal copy the underlying JSON into go interface, same as json.Unmarshal +func (iter *Iterator) ReadVal(obj interface{}) { + depth := iter.depth + cacheKey := reflect2.RTypeOf(obj) + decoder := iter.cfg.getDecoderFromCache(cacheKey) + if decoder == nil { + typ := reflect2.TypeOf(obj) + if typ == nil || typ.Kind() != reflect.Ptr { + iter.ReportError("ReadVal", "can only unmarshal into pointer") + return + } + decoder = iter.cfg.DecoderOf(typ) + } + ptr := reflect2.PtrOf(obj) + if ptr == nil { + iter.ReportError("ReadVal", "can not read into nil pointer") + return + } + decoder.Decode(ptr, iter) + if iter.depth != depth { + iter.ReportError("ReadVal", "unexpected mismatched nesting") + return + } +} + +// WriteVal copy the go interface into underlying JSON, same as json.Marshal +func (stream *Stream) WriteVal(val interface{}) { + if nil == val { + stream.WriteNil() + return + } + cacheKey := reflect2.RTypeOf(val) + encoder := stream.cfg.getEncoderFromCache(cacheKey) + if encoder == nil { + typ := reflect2.TypeOf(val) + encoder = stream.cfg.EncoderOf(typ) + } + encoder.Encode(reflect2.PtrOf(val), stream) +} + +func (cfg *frozenConfig) DecoderOf(typ reflect2.Type) ValDecoder { + cacheKey := typ.RType() + decoder := cfg.getDecoderFromCache(cacheKey) + if decoder != nil { + return decoder + } + ctx := &ctx{ + frozenConfig: cfg, + prefix: "", + decoders: map[reflect2.Type]ValDecoder{}, + encoders: map[reflect2.Type]ValEncoder{}, + } + ptrType := typ.(*reflect2.UnsafePtrType) + decoder = decoderOfType(ctx, ptrType.Elem()) + cfg.addDecoderToCache(cacheKey, decoder) + return decoder +} + +func decoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder { + decoder := getTypeDecoderFromExtension(ctx, typ) + if decoder != nil { + return decoder + } + decoder = createDecoderOfType(ctx, typ) + for _, extension := range extensions { + decoder = extension.DecorateDecoder(typ, decoder) + } + decoder = ctx.decoderExtension.DecorateDecoder(typ, decoder) + for _, extension := range ctx.extraExtensions { + decoder = extension.DecorateDecoder(typ, decoder) + } + return decoder +} + +func createDecoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder { + decoder := ctx.decoders[typ] + if decoder != nil { + return decoder + } + placeholder := &placeholderDecoder{} + ctx.decoders[typ] = placeholder + decoder = _createDecoderOfType(ctx, typ) + placeholder.decoder = decoder + return decoder +} + +func _createDecoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder { + decoder := createDecoderOfJsonRawMessage(ctx, typ) + if decoder != nil { + return decoder + } + decoder = createDecoderOfJsonNumber(ctx, typ) + if decoder != nil { + return decoder + } + decoder = createDecoderOfMarshaler(ctx, typ) + if decoder != nil { + return decoder + } + decoder = createDecoderOfAny(ctx, typ) + if decoder != nil { + return decoder + } + decoder = createDecoderOfNative(ctx, typ) + if decoder != nil { + return decoder + } + switch typ.Kind() { + case reflect.Interface: + ifaceType, isIFace := typ.(*reflect2.UnsafeIFaceType) + if isIFace { + return &ifaceDecoder{valType: ifaceType} + } + return &efaceDecoder{} + case reflect.Struct: + return decoderOfStruct(ctx, typ) + case reflect.Array: + return decoderOfArray(ctx, typ) + case reflect.Slice: + return decoderOfSlice(ctx, typ) + case reflect.Map: + return decoderOfMap(ctx, typ) + case reflect.Ptr: + return decoderOfOptional(ctx, typ) + default: + return &lazyErrorDecoder{err: fmt.Errorf("%s%s is unsupported type", ctx.prefix, typ.String())} + } +} + +func (cfg *frozenConfig) EncoderOf(typ reflect2.Type) ValEncoder { + cacheKey := typ.RType() + encoder := cfg.getEncoderFromCache(cacheKey) + if encoder != nil { + return encoder + } + ctx := &ctx{ + frozenConfig: cfg, + prefix: "", + decoders: map[reflect2.Type]ValDecoder{}, + encoders: map[reflect2.Type]ValEncoder{}, + } + encoder = encoderOfType(ctx, typ) + if typ.LikePtr() { + encoder = &onePtrEncoder{encoder} + } + cfg.addEncoderToCache(cacheKey, encoder) + return encoder +} + +type onePtrEncoder struct { + encoder ValEncoder +} + +func (encoder *onePtrEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.encoder.IsEmpty(unsafe.Pointer(&ptr)) +} + +func (encoder *onePtrEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + encoder.encoder.Encode(unsafe.Pointer(&ptr), stream) +} + +func encoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder { + encoder := getTypeEncoderFromExtension(ctx, typ) + if encoder != nil { + return encoder + } + encoder = createEncoderOfType(ctx, typ) + for _, extension := range extensions { + encoder = extension.DecorateEncoder(typ, encoder) + } + encoder = ctx.encoderExtension.DecorateEncoder(typ, encoder) + for _, extension := range ctx.extraExtensions { + encoder = extension.DecorateEncoder(typ, encoder) + } + return encoder +} + +func createEncoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder { + encoder := ctx.encoders[typ] + if encoder != nil { + return encoder + } + placeholder := &placeholderEncoder{} + ctx.encoders[typ] = placeholder + encoder = _createEncoderOfType(ctx, typ) + placeholder.encoder = encoder + return encoder +} +func _createEncoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder { + encoder := createEncoderOfJsonRawMessage(ctx, typ) + if encoder != nil { + return encoder + } + encoder = createEncoderOfJsonNumber(ctx, typ) + if encoder != nil { + return encoder + } + encoder = createEncoderOfMarshaler(ctx, typ) + if encoder != nil { + return encoder + } + encoder = createEncoderOfAny(ctx, typ) + if encoder != nil { + return encoder + } + encoder = createEncoderOfNative(ctx, typ) + if encoder != nil { + return encoder + } + kind := typ.Kind() + switch kind { + case reflect.Interface: + return &dynamicEncoder{typ} + case reflect.Struct: + return encoderOfStruct(ctx, typ) + case reflect.Array: + return encoderOfArray(ctx, typ) + case reflect.Slice: + return encoderOfSlice(ctx, typ) + case reflect.Map: + return encoderOfMap(ctx, typ) + case reflect.Ptr: + return encoderOfOptional(ctx, typ) + default: + return &lazyErrorEncoder{err: fmt.Errorf("%s%s is unsupported type", ctx.prefix, typ.String())} + } +} + +type lazyErrorDecoder struct { + err error +} + +func (decoder *lazyErrorDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if iter.WhatIsNext() != NilValue { + if iter.Error == nil { + iter.Error = decoder.err + } + } else { + iter.Skip() + } +} + +type lazyErrorEncoder struct { + err error +} + +func (encoder *lazyErrorEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + if ptr == nil { + stream.WriteNil() + } else if stream.Error == nil { + stream.Error = encoder.err + } +} + +func (encoder *lazyErrorEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return false +} + +type placeholderDecoder struct { + decoder ValDecoder +} + +func (decoder *placeholderDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + decoder.decoder.Decode(ptr, iter) +} + +type placeholderEncoder struct { + encoder ValEncoder +} + +func (encoder *placeholderEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + encoder.encoder.Encode(ptr, stream) +} + +func (encoder *placeholderEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.encoder.IsEmpty(ptr) +} diff --git a/vendor/github.com/json-iterator/go/reflect_array.go b/vendor/github.com/json-iterator/go/reflect_array.go new file mode 100644 index 00000000..13a0b7b0 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_array.go @@ -0,0 +1,104 @@ +package jsoniter + +import ( + "fmt" + "github.com/modern-go/reflect2" + "io" + "unsafe" +) + +func decoderOfArray(ctx *ctx, typ reflect2.Type) ValDecoder { + arrayType := typ.(*reflect2.UnsafeArrayType) + decoder := decoderOfType(ctx.append("[arrayElem]"), arrayType.Elem()) + return &arrayDecoder{arrayType, decoder} +} + +func encoderOfArray(ctx *ctx, typ reflect2.Type) ValEncoder { + arrayType := typ.(*reflect2.UnsafeArrayType) + if arrayType.Len() == 0 { + return emptyArrayEncoder{} + } + encoder := encoderOfType(ctx.append("[arrayElem]"), arrayType.Elem()) + return &arrayEncoder{arrayType, encoder} +} + +type emptyArrayEncoder struct{} + +func (encoder emptyArrayEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteEmptyArray() +} + +func (encoder emptyArrayEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return true +} + +type arrayEncoder struct { + arrayType *reflect2.UnsafeArrayType + elemEncoder ValEncoder +} + +func (encoder *arrayEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteArrayStart() + elemPtr := unsafe.Pointer(ptr) + encoder.elemEncoder.Encode(elemPtr, stream) + for i := 1; i < encoder.arrayType.Len(); i++ { + stream.WriteMore() + elemPtr = encoder.arrayType.UnsafeGetIndex(ptr, i) + encoder.elemEncoder.Encode(elemPtr, stream) + } + stream.WriteArrayEnd() + if stream.Error != nil && stream.Error != io.EOF { + stream.Error = fmt.Errorf("%v: %s", encoder.arrayType, stream.Error.Error()) + } +} + +func (encoder *arrayEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return false +} + +type arrayDecoder struct { + arrayType *reflect2.UnsafeArrayType + elemDecoder ValDecoder +} + +func (decoder *arrayDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + decoder.doDecode(ptr, iter) + if iter.Error != nil && iter.Error != io.EOF { + iter.Error = fmt.Errorf("%v: %s", decoder.arrayType, iter.Error.Error()) + } +} + +func (decoder *arrayDecoder) doDecode(ptr unsafe.Pointer, iter *Iterator) { + c := iter.nextToken() + arrayType := decoder.arrayType + if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') + return + } + if c != '[' { + iter.ReportError("decode array", "expect [ or n, but found "+string([]byte{c})) + return + } + c = iter.nextToken() + if c == ']' { + return + } + iter.unreadByte() + elemPtr := arrayType.UnsafeGetIndex(ptr, 0) + decoder.elemDecoder.Decode(elemPtr, iter) + length := 1 + for c = iter.nextToken(); c == ','; c = iter.nextToken() { + if length >= arrayType.Len() { + iter.Skip() + continue + } + idx := length + length += 1 + elemPtr = arrayType.UnsafeGetIndex(ptr, idx) + decoder.elemDecoder.Decode(elemPtr, iter) + } + if c != ']' { + iter.ReportError("decode array", "expect ], but found "+string([]byte{c})) + return + } +} diff --git a/vendor/github.com/json-iterator/go/reflect_dynamic.go b/vendor/github.com/json-iterator/go/reflect_dynamic.go new file mode 100644 index 00000000..8b6bc8b4 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_dynamic.go @@ -0,0 +1,70 @@ +package jsoniter + +import ( + "github.com/modern-go/reflect2" + "reflect" + "unsafe" +) + +type dynamicEncoder struct { + valType reflect2.Type +} + +func (encoder *dynamicEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + obj := encoder.valType.UnsafeIndirect(ptr) + stream.WriteVal(obj) +} + +func (encoder *dynamicEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.valType.UnsafeIndirect(ptr) == nil +} + +type efaceDecoder struct { +} + +func (decoder *efaceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + pObj := (*interface{})(ptr) + obj := *pObj + if obj == nil { + *pObj = iter.Read() + return + } + typ := reflect2.TypeOf(obj) + if typ.Kind() != reflect.Ptr { + *pObj = iter.Read() + return + } + ptrType := typ.(*reflect2.UnsafePtrType) + ptrElemType := ptrType.Elem() + if iter.WhatIsNext() == NilValue { + if ptrElemType.Kind() != reflect.Ptr { + iter.skipFourBytes('n', 'u', 'l', 'l') + *pObj = nil + return + } + } + if reflect2.IsNil(obj) { + obj := ptrElemType.New() + iter.ReadVal(obj) + *pObj = obj + return + } + iter.ReadVal(obj) +} + +type ifaceDecoder struct { + valType *reflect2.UnsafeIFaceType +} + +func (decoder *ifaceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if iter.ReadNil() { + decoder.valType.UnsafeSet(ptr, decoder.valType.UnsafeNew()) + return + } + obj := decoder.valType.UnsafeIndirect(ptr) + if reflect2.IsNil(obj) { + iter.ReportError("decode non empty interface", "can not unmarshal into nil") + return + } + iter.ReadVal(obj) +} diff --git a/vendor/github.com/json-iterator/go/reflect_extension.go b/vendor/github.com/json-iterator/go/reflect_extension.go new file mode 100644 index 00000000..74a97bfe --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_extension.go @@ -0,0 +1,483 @@ +package jsoniter + +import ( + "fmt" + "github.com/modern-go/reflect2" + "reflect" + "sort" + "strings" + "unicode" + "unsafe" +) + +var typeDecoders = map[string]ValDecoder{} +var fieldDecoders = map[string]ValDecoder{} +var typeEncoders = map[string]ValEncoder{} +var fieldEncoders = map[string]ValEncoder{} +var extensions = []Extension{} + +// StructDescriptor describe how should we encode/decode the struct +type StructDescriptor struct { + Type reflect2.Type + Fields []*Binding +} + +// GetField get one field from the descriptor by its name. +// Can not use map here to keep field orders. +func (structDescriptor *StructDescriptor) GetField(fieldName string) *Binding { + for _, binding := range structDescriptor.Fields { + if binding.Field.Name() == fieldName { + return binding + } + } + return nil +} + +// Binding describe how should we encode/decode the struct field +type Binding struct { + levels []int + Field reflect2.StructField + FromNames []string + ToNames []string + Encoder ValEncoder + Decoder ValDecoder +} + +// Extension the one for all SPI. Customize encoding/decoding by specifying alternate encoder/decoder. +// Can also rename fields by UpdateStructDescriptor. +type Extension interface { + UpdateStructDescriptor(structDescriptor *StructDescriptor) + CreateMapKeyDecoder(typ reflect2.Type) ValDecoder + CreateMapKeyEncoder(typ reflect2.Type) ValEncoder + CreateDecoder(typ reflect2.Type) ValDecoder + CreateEncoder(typ reflect2.Type) ValEncoder + DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder + DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder +} + +// DummyExtension embed this type get dummy implementation for all methods of Extension +type DummyExtension struct { +} + +// UpdateStructDescriptor No-op +func (extension *DummyExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) { +} + +// CreateMapKeyDecoder No-op +func (extension *DummyExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder { + return nil +} + +// CreateMapKeyEncoder No-op +func (extension *DummyExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder { + return nil +} + +// CreateDecoder No-op +func (extension *DummyExtension) CreateDecoder(typ reflect2.Type) ValDecoder { + return nil +} + +// CreateEncoder No-op +func (extension *DummyExtension) CreateEncoder(typ reflect2.Type) ValEncoder { + return nil +} + +// DecorateDecoder No-op +func (extension *DummyExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder { + return decoder +} + +// DecorateEncoder No-op +func (extension *DummyExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder { + return encoder +} + +type EncoderExtension map[reflect2.Type]ValEncoder + +// UpdateStructDescriptor No-op +func (extension EncoderExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) { +} + +// CreateDecoder No-op +func (extension EncoderExtension) CreateDecoder(typ reflect2.Type) ValDecoder { + return nil +} + +// CreateEncoder get encoder from map +func (extension EncoderExtension) CreateEncoder(typ reflect2.Type) ValEncoder { + return extension[typ] +} + +// CreateMapKeyDecoder No-op +func (extension EncoderExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder { + return nil +} + +// CreateMapKeyEncoder No-op +func (extension EncoderExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder { + return nil +} + +// DecorateDecoder No-op +func (extension EncoderExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder { + return decoder +} + +// DecorateEncoder No-op +func (extension EncoderExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder { + return encoder +} + +type DecoderExtension map[reflect2.Type]ValDecoder + +// UpdateStructDescriptor No-op +func (extension DecoderExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) { +} + +// CreateMapKeyDecoder No-op +func (extension DecoderExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder { + return nil +} + +// CreateMapKeyEncoder No-op +func (extension DecoderExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder { + return nil +} + +// CreateDecoder get decoder from map +func (extension DecoderExtension) CreateDecoder(typ reflect2.Type) ValDecoder { + return extension[typ] +} + +// CreateEncoder No-op +func (extension DecoderExtension) CreateEncoder(typ reflect2.Type) ValEncoder { + return nil +} + +// DecorateDecoder No-op +func (extension DecoderExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder { + return decoder +} + +// DecorateEncoder No-op +func (extension DecoderExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder { + return encoder +} + +type funcDecoder struct { + fun DecoderFunc +} + +func (decoder *funcDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + decoder.fun(ptr, iter) +} + +type funcEncoder struct { + fun EncoderFunc + isEmptyFunc func(ptr unsafe.Pointer) bool +} + +func (encoder *funcEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + encoder.fun(ptr, stream) +} + +func (encoder *funcEncoder) IsEmpty(ptr unsafe.Pointer) bool { + if encoder.isEmptyFunc == nil { + return false + } + return encoder.isEmptyFunc(ptr) +} + +// DecoderFunc the function form of TypeDecoder +type DecoderFunc func(ptr unsafe.Pointer, iter *Iterator) + +// EncoderFunc the function form of TypeEncoder +type EncoderFunc func(ptr unsafe.Pointer, stream *Stream) + +// RegisterTypeDecoderFunc register TypeDecoder for a type with function +func RegisterTypeDecoderFunc(typ string, fun DecoderFunc) { + typeDecoders[typ] = &funcDecoder{fun} +} + +// RegisterTypeDecoder register TypeDecoder for a typ +func RegisterTypeDecoder(typ string, decoder ValDecoder) { + typeDecoders[typ] = decoder +} + +// RegisterFieldDecoderFunc register TypeDecoder for a struct field with function +func RegisterFieldDecoderFunc(typ string, field string, fun DecoderFunc) { + RegisterFieldDecoder(typ, field, &funcDecoder{fun}) +} + +// RegisterFieldDecoder register TypeDecoder for a struct field +func RegisterFieldDecoder(typ string, field string, decoder ValDecoder) { + fieldDecoders[fmt.Sprintf("%s/%s", typ, field)] = decoder +} + +// RegisterTypeEncoderFunc register TypeEncoder for a type with encode/isEmpty function +func RegisterTypeEncoderFunc(typ string, fun EncoderFunc, isEmptyFunc func(unsafe.Pointer) bool) { + typeEncoders[typ] = &funcEncoder{fun, isEmptyFunc} +} + +// RegisterTypeEncoder register TypeEncoder for a type +func RegisterTypeEncoder(typ string, encoder ValEncoder) { + typeEncoders[typ] = encoder +} + +// RegisterFieldEncoderFunc register TypeEncoder for a struct field with encode/isEmpty function +func RegisterFieldEncoderFunc(typ string, field string, fun EncoderFunc, isEmptyFunc func(unsafe.Pointer) bool) { + RegisterFieldEncoder(typ, field, &funcEncoder{fun, isEmptyFunc}) +} + +// RegisterFieldEncoder register TypeEncoder for a struct field +func RegisterFieldEncoder(typ string, field string, encoder ValEncoder) { + fieldEncoders[fmt.Sprintf("%s/%s", typ, field)] = encoder +} + +// RegisterExtension register extension +func RegisterExtension(extension Extension) { + extensions = append(extensions, extension) +} + +func getTypeDecoderFromExtension(ctx *ctx, typ reflect2.Type) ValDecoder { + decoder := _getTypeDecoderFromExtension(ctx, typ) + if decoder != nil { + for _, extension := range extensions { + decoder = extension.DecorateDecoder(typ, decoder) + } + decoder = ctx.decoderExtension.DecorateDecoder(typ, decoder) + for _, extension := range ctx.extraExtensions { + decoder = extension.DecorateDecoder(typ, decoder) + } + } + return decoder +} +func _getTypeDecoderFromExtension(ctx *ctx, typ reflect2.Type) ValDecoder { + for _, extension := range extensions { + decoder := extension.CreateDecoder(typ) + if decoder != nil { + return decoder + } + } + decoder := ctx.decoderExtension.CreateDecoder(typ) + if decoder != nil { + return decoder + } + for _, extension := range ctx.extraExtensions { + decoder := extension.CreateDecoder(typ) + if decoder != nil { + return decoder + } + } + typeName := typ.String() + decoder = typeDecoders[typeName] + if decoder != nil { + return decoder + } + if typ.Kind() == reflect.Ptr { + ptrType := typ.(*reflect2.UnsafePtrType) + decoder := typeDecoders[ptrType.Elem().String()] + if decoder != nil { + return &OptionalDecoder{ptrType.Elem(), decoder} + } + } + return nil +} + +func getTypeEncoderFromExtension(ctx *ctx, typ reflect2.Type) ValEncoder { + encoder := _getTypeEncoderFromExtension(ctx, typ) + if encoder != nil { + for _, extension := range extensions { + encoder = extension.DecorateEncoder(typ, encoder) + } + encoder = ctx.encoderExtension.DecorateEncoder(typ, encoder) + for _, extension := range ctx.extraExtensions { + encoder = extension.DecorateEncoder(typ, encoder) + } + } + return encoder +} + +func _getTypeEncoderFromExtension(ctx *ctx, typ reflect2.Type) ValEncoder { + for _, extension := range extensions { + encoder := extension.CreateEncoder(typ) + if encoder != nil { + return encoder + } + } + encoder := ctx.encoderExtension.CreateEncoder(typ) + if encoder != nil { + return encoder + } + for _, extension := range ctx.extraExtensions { + encoder := extension.CreateEncoder(typ) + if encoder != nil { + return encoder + } + } + typeName := typ.String() + encoder = typeEncoders[typeName] + if encoder != nil { + return encoder + } + if typ.Kind() == reflect.Ptr { + typePtr := typ.(*reflect2.UnsafePtrType) + encoder := typeEncoders[typePtr.Elem().String()] + if encoder != nil { + return &OptionalEncoder{encoder} + } + } + return nil +} + +func describeStruct(ctx *ctx, typ reflect2.Type) *StructDescriptor { + structType := typ.(*reflect2.UnsafeStructType) + embeddedBindings := []*Binding{} + bindings := []*Binding{} + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + tag, hastag := field.Tag().Lookup(ctx.getTagKey()) + if ctx.onlyTaggedField && !hastag && !field.Anonymous() { + continue + } + if tag == "-" || field.Name() == "_" { + continue + } + tagParts := strings.Split(tag, ",") + if field.Anonymous() && (tag == "" || tagParts[0] == "") { + if field.Type().Kind() == reflect.Struct { + structDescriptor := describeStruct(ctx, field.Type()) + for _, binding := range structDescriptor.Fields { + binding.levels = append([]int{i}, binding.levels...) + omitempty := binding.Encoder.(*structFieldEncoder).omitempty + binding.Encoder = &structFieldEncoder{field, binding.Encoder, omitempty} + binding.Decoder = &structFieldDecoder{field, binding.Decoder} + embeddedBindings = append(embeddedBindings, binding) + } + continue + } else if field.Type().Kind() == reflect.Ptr { + ptrType := field.Type().(*reflect2.UnsafePtrType) + if ptrType.Elem().Kind() == reflect.Struct { + structDescriptor := describeStruct(ctx, ptrType.Elem()) + for _, binding := range structDescriptor.Fields { + binding.levels = append([]int{i}, binding.levels...) + omitempty := binding.Encoder.(*structFieldEncoder).omitempty + binding.Encoder = &dereferenceEncoder{binding.Encoder} + binding.Encoder = &structFieldEncoder{field, binding.Encoder, omitempty} + binding.Decoder = &dereferenceDecoder{ptrType.Elem(), binding.Decoder} + binding.Decoder = &structFieldDecoder{field, binding.Decoder} + embeddedBindings = append(embeddedBindings, binding) + } + continue + } + } + } + fieldNames := calcFieldNames(field.Name(), tagParts[0], tag) + fieldCacheKey := fmt.Sprintf("%s/%s", typ.String(), field.Name()) + decoder := fieldDecoders[fieldCacheKey] + if decoder == nil { + decoder = decoderOfType(ctx.append(field.Name()), field.Type()) + } + encoder := fieldEncoders[fieldCacheKey] + if encoder == nil { + encoder = encoderOfType(ctx.append(field.Name()), field.Type()) + } + binding := &Binding{ + Field: field, + FromNames: fieldNames, + ToNames: fieldNames, + Decoder: decoder, + Encoder: encoder, + } + binding.levels = []int{i} + bindings = append(bindings, binding) + } + return createStructDescriptor(ctx, typ, bindings, embeddedBindings) +} +func createStructDescriptor(ctx *ctx, typ reflect2.Type, bindings []*Binding, embeddedBindings []*Binding) *StructDescriptor { + structDescriptor := &StructDescriptor{ + Type: typ, + Fields: bindings, + } + for _, extension := range extensions { + extension.UpdateStructDescriptor(structDescriptor) + } + ctx.encoderExtension.UpdateStructDescriptor(structDescriptor) + ctx.decoderExtension.UpdateStructDescriptor(structDescriptor) + for _, extension := range ctx.extraExtensions { + extension.UpdateStructDescriptor(structDescriptor) + } + processTags(structDescriptor, ctx.frozenConfig) + // merge normal & embedded bindings & sort with original order + allBindings := sortableBindings(append(embeddedBindings, structDescriptor.Fields...)) + sort.Sort(allBindings) + structDescriptor.Fields = allBindings + return structDescriptor +} + +type sortableBindings []*Binding + +func (bindings sortableBindings) Len() int { + return len(bindings) +} + +func (bindings sortableBindings) Less(i, j int) bool { + left := bindings[i].levels + right := bindings[j].levels + k := 0 + for { + if left[k] < right[k] { + return true + } else if left[k] > right[k] { + return false + } + k++ + } +} + +func (bindings sortableBindings) Swap(i, j int) { + bindings[i], bindings[j] = bindings[j], bindings[i] +} + +func processTags(structDescriptor *StructDescriptor, cfg *frozenConfig) { + for _, binding := range structDescriptor.Fields { + shouldOmitEmpty := false + tagParts := strings.Split(binding.Field.Tag().Get(cfg.getTagKey()), ",") + for _, tagPart := range tagParts[1:] { + if tagPart == "omitempty" { + shouldOmitEmpty = true + } else if tagPart == "string" { + if binding.Field.Type().Kind() == reflect.String { + binding.Decoder = &stringModeStringDecoder{binding.Decoder, cfg} + binding.Encoder = &stringModeStringEncoder{binding.Encoder, cfg} + } else { + binding.Decoder = &stringModeNumberDecoder{binding.Decoder} + binding.Encoder = &stringModeNumberEncoder{binding.Encoder} + } + } + } + binding.Decoder = &structFieldDecoder{binding.Field, binding.Decoder} + binding.Encoder = &structFieldEncoder{binding.Field, binding.Encoder, shouldOmitEmpty} + } +} + +func calcFieldNames(originalFieldName string, tagProvidedFieldName string, wholeTag string) []string { + // ignore? + if wholeTag == "-" { + return []string{} + } + // rename? + var fieldNames []string + if tagProvidedFieldName == "" { + fieldNames = []string{originalFieldName} + } else { + fieldNames = []string{tagProvidedFieldName} + } + // private? + isNotExported := unicode.IsLower(rune(originalFieldName[0])) || originalFieldName[0] == '_' + if isNotExported { + fieldNames = []string{} + } + return fieldNames +} diff --git a/vendor/github.com/json-iterator/go/reflect_json_number.go b/vendor/github.com/json-iterator/go/reflect_json_number.go new file mode 100644 index 00000000..98d45c1e --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_json_number.go @@ -0,0 +1,112 @@ +package jsoniter + +import ( + "encoding/json" + "github.com/modern-go/reflect2" + "strconv" + "unsafe" +) + +type Number string + +// String returns the literal text of the number. +func (n Number) String() string { return string(n) } + +// Float64 returns the number as a float64. +func (n Number) Float64() (float64, error) { + return strconv.ParseFloat(string(n), 64) +} + +// Int64 returns the number as an int64. +func (n Number) Int64() (int64, error) { + return strconv.ParseInt(string(n), 10, 64) +} + +func CastJsonNumber(val interface{}) (string, bool) { + switch typedVal := val.(type) { + case json.Number: + return string(typedVal), true + case Number: + return string(typedVal), true + } + return "", false +} + +var jsonNumberType = reflect2.TypeOfPtr((*json.Number)(nil)).Elem() +var jsoniterNumberType = reflect2.TypeOfPtr((*Number)(nil)).Elem() + +func createDecoderOfJsonNumber(ctx *ctx, typ reflect2.Type) ValDecoder { + if typ.AssignableTo(jsonNumberType) { + return &jsonNumberCodec{} + } + if typ.AssignableTo(jsoniterNumberType) { + return &jsoniterNumberCodec{} + } + return nil +} + +func createEncoderOfJsonNumber(ctx *ctx, typ reflect2.Type) ValEncoder { + if typ.AssignableTo(jsonNumberType) { + return &jsonNumberCodec{} + } + if typ.AssignableTo(jsoniterNumberType) { + return &jsoniterNumberCodec{} + } + return nil +} + +type jsonNumberCodec struct { +} + +func (codec *jsonNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + switch iter.WhatIsNext() { + case StringValue: + *((*json.Number)(ptr)) = json.Number(iter.ReadString()) + case NilValue: + iter.skipFourBytes('n', 'u', 'l', 'l') + *((*json.Number)(ptr)) = "" + default: + *((*json.Number)(ptr)) = json.Number([]byte(iter.readNumberAsString())) + } +} + +func (codec *jsonNumberCodec) Encode(ptr unsafe.Pointer, stream *Stream) { + number := *((*json.Number)(ptr)) + if len(number) == 0 { + stream.writeByte('0') + } else { + stream.WriteRaw(string(number)) + } +} + +func (codec *jsonNumberCodec) IsEmpty(ptr unsafe.Pointer) bool { + return len(*((*json.Number)(ptr))) == 0 +} + +type jsoniterNumberCodec struct { +} + +func (codec *jsoniterNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + switch iter.WhatIsNext() { + case StringValue: + *((*Number)(ptr)) = Number(iter.ReadString()) + case NilValue: + iter.skipFourBytes('n', 'u', 'l', 'l') + *((*Number)(ptr)) = "" + default: + *((*Number)(ptr)) = Number([]byte(iter.readNumberAsString())) + } +} + +func (codec *jsoniterNumberCodec) Encode(ptr unsafe.Pointer, stream *Stream) { + number := *((*Number)(ptr)) + if len(number) == 0 { + stream.writeByte('0') + } else { + stream.WriteRaw(string(number)) + } +} + +func (codec *jsoniterNumberCodec) IsEmpty(ptr unsafe.Pointer) bool { + return len(*((*Number)(ptr))) == 0 +} diff --git a/vendor/github.com/json-iterator/go/reflect_json_raw_message.go b/vendor/github.com/json-iterator/go/reflect_json_raw_message.go new file mode 100644 index 00000000..eba434f2 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_json_raw_message.go @@ -0,0 +1,76 @@ +package jsoniter + +import ( + "encoding/json" + "github.com/modern-go/reflect2" + "unsafe" +) + +var jsonRawMessageType = reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem() +var jsoniterRawMessageType = reflect2.TypeOfPtr((*RawMessage)(nil)).Elem() + +func createEncoderOfJsonRawMessage(ctx *ctx, typ reflect2.Type) ValEncoder { + if typ == jsonRawMessageType { + return &jsonRawMessageCodec{} + } + if typ == jsoniterRawMessageType { + return &jsoniterRawMessageCodec{} + } + return nil +} + +func createDecoderOfJsonRawMessage(ctx *ctx, typ reflect2.Type) ValDecoder { + if typ == jsonRawMessageType { + return &jsonRawMessageCodec{} + } + if typ == jsoniterRawMessageType { + return &jsoniterRawMessageCodec{} + } + return nil +} + +type jsonRawMessageCodec struct { +} + +func (codec *jsonRawMessageCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if iter.ReadNil() { + *((*json.RawMessage)(ptr)) = nil + } else { + *((*json.RawMessage)(ptr)) = iter.SkipAndReturnBytes() + } +} + +func (codec *jsonRawMessageCodec) Encode(ptr unsafe.Pointer, stream *Stream) { + if *((*json.RawMessage)(ptr)) == nil { + stream.WriteNil() + } else { + stream.WriteRaw(string(*((*json.RawMessage)(ptr)))) + } +} + +func (codec *jsonRawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool { + return len(*((*json.RawMessage)(ptr))) == 0 +} + +type jsoniterRawMessageCodec struct { +} + +func (codec *jsoniterRawMessageCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if iter.ReadNil() { + *((*RawMessage)(ptr)) = nil + } else { + *((*RawMessage)(ptr)) = iter.SkipAndReturnBytes() + } +} + +func (codec *jsoniterRawMessageCodec) Encode(ptr unsafe.Pointer, stream *Stream) { + if *((*RawMessage)(ptr)) == nil { + stream.WriteNil() + } else { + stream.WriteRaw(string(*((*RawMessage)(ptr)))) + } +} + +func (codec *jsoniterRawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool { + return len(*((*RawMessage)(ptr))) == 0 +} diff --git a/vendor/github.com/json-iterator/go/reflect_map.go b/vendor/github.com/json-iterator/go/reflect_map.go new file mode 100644 index 00000000..58296713 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_map.go @@ -0,0 +1,346 @@ +package jsoniter + +import ( + "fmt" + "github.com/modern-go/reflect2" + "io" + "reflect" + "sort" + "unsafe" +) + +func decoderOfMap(ctx *ctx, typ reflect2.Type) ValDecoder { + mapType := typ.(*reflect2.UnsafeMapType) + keyDecoder := decoderOfMapKey(ctx.append("[mapKey]"), mapType.Key()) + elemDecoder := decoderOfType(ctx.append("[mapElem]"), mapType.Elem()) + return &mapDecoder{ + mapType: mapType, + keyType: mapType.Key(), + elemType: mapType.Elem(), + keyDecoder: keyDecoder, + elemDecoder: elemDecoder, + } +} + +func encoderOfMap(ctx *ctx, typ reflect2.Type) ValEncoder { + mapType := typ.(*reflect2.UnsafeMapType) + if ctx.sortMapKeys { + return &sortKeysMapEncoder{ + mapType: mapType, + keyEncoder: encoderOfMapKey(ctx.append("[mapKey]"), mapType.Key()), + elemEncoder: encoderOfType(ctx.append("[mapElem]"), mapType.Elem()), + } + } + return &mapEncoder{ + mapType: mapType, + keyEncoder: encoderOfMapKey(ctx.append("[mapKey]"), mapType.Key()), + elemEncoder: encoderOfType(ctx.append("[mapElem]"), mapType.Elem()), + } +} + +func decoderOfMapKey(ctx *ctx, typ reflect2.Type) ValDecoder { + decoder := ctx.decoderExtension.CreateMapKeyDecoder(typ) + if decoder != nil { + return decoder + } + for _, extension := range ctx.extraExtensions { + decoder := extension.CreateMapKeyDecoder(typ) + if decoder != nil { + return decoder + } + } + + ptrType := reflect2.PtrTo(typ) + if ptrType.Implements(unmarshalerType) { + return &referenceDecoder{ + &unmarshalerDecoder{ + valType: ptrType, + }, + } + } + if typ.Implements(unmarshalerType) { + return &unmarshalerDecoder{ + valType: typ, + } + } + if ptrType.Implements(textUnmarshalerType) { + return &referenceDecoder{ + &textUnmarshalerDecoder{ + valType: ptrType, + }, + } + } + if typ.Implements(textUnmarshalerType) { + return &textUnmarshalerDecoder{ + valType: typ, + } + } + + switch typ.Kind() { + case reflect.String: + return decoderOfType(ctx, reflect2.DefaultTypeOfKind(reflect.String)) + case reflect.Bool, + reflect.Uint8, reflect.Int8, + reflect.Uint16, reflect.Int16, + reflect.Uint32, reflect.Int32, + reflect.Uint64, reflect.Int64, + reflect.Uint, reflect.Int, + reflect.Float32, reflect.Float64, + reflect.Uintptr: + typ = reflect2.DefaultTypeOfKind(typ.Kind()) + return &numericMapKeyDecoder{decoderOfType(ctx, typ)} + default: + return &lazyErrorDecoder{err: fmt.Errorf("unsupported map key type: %v", typ)} + } +} + +func encoderOfMapKey(ctx *ctx, typ reflect2.Type) ValEncoder { + encoder := ctx.encoderExtension.CreateMapKeyEncoder(typ) + if encoder != nil { + return encoder + } + for _, extension := range ctx.extraExtensions { + encoder := extension.CreateMapKeyEncoder(typ) + if encoder != nil { + return encoder + } + } + + if typ == textMarshalerType { + return &directTextMarshalerEncoder{ + stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), + } + } + if typ.Implements(textMarshalerType) { + return &textMarshalerEncoder{ + valType: typ, + stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), + } + } + + switch typ.Kind() { + case reflect.String: + return encoderOfType(ctx, reflect2.DefaultTypeOfKind(reflect.String)) + case reflect.Bool, + reflect.Uint8, reflect.Int8, + reflect.Uint16, reflect.Int16, + reflect.Uint32, reflect.Int32, + reflect.Uint64, reflect.Int64, + reflect.Uint, reflect.Int, + reflect.Float32, reflect.Float64, + reflect.Uintptr: + typ = reflect2.DefaultTypeOfKind(typ.Kind()) + return &numericMapKeyEncoder{encoderOfType(ctx, typ)} + default: + if typ.Kind() == reflect.Interface { + return &dynamicMapKeyEncoder{ctx, typ} + } + return &lazyErrorEncoder{err: fmt.Errorf("unsupported map key type: %v", typ)} + } +} + +type mapDecoder struct { + mapType *reflect2.UnsafeMapType + keyType reflect2.Type + elemType reflect2.Type + keyDecoder ValDecoder + elemDecoder ValDecoder +} + +func (decoder *mapDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + mapType := decoder.mapType + c := iter.nextToken() + if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') + *(*unsafe.Pointer)(ptr) = nil + mapType.UnsafeSet(ptr, mapType.UnsafeNew()) + return + } + if mapType.UnsafeIsNil(ptr) { + mapType.UnsafeSet(ptr, mapType.UnsafeMakeMap(0)) + } + if c != '{' { + iter.ReportError("ReadMapCB", `expect { or n, but found `+string([]byte{c})) + return + } + c = iter.nextToken() + if c == '}' { + return + } + iter.unreadByte() + key := decoder.keyType.UnsafeNew() + decoder.keyDecoder.Decode(key, iter) + c = iter.nextToken() + if c != ':' { + iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c})) + return + } + elem := decoder.elemType.UnsafeNew() + decoder.elemDecoder.Decode(elem, iter) + decoder.mapType.UnsafeSetIndex(ptr, key, elem) + for c = iter.nextToken(); c == ','; c = iter.nextToken() { + key := decoder.keyType.UnsafeNew() + decoder.keyDecoder.Decode(key, iter) + c = iter.nextToken() + if c != ':' { + iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c})) + return + } + elem := decoder.elemType.UnsafeNew() + decoder.elemDecoder.Decode(elem, iter) + decoder.mapType.UnsafeSetIndex(ptr, key, elem) + } + if c != '}' { + iter.ReportError("ReadMapCB", `expect }, but found `+string([]byte{c})) + } +} + +type numericMapKeyDecoder struct { + decoder ValDecoder +} + +func (decoder *numericMapKeyDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + c := iter.nextToken() + if c != '"' { + iter.ReportError("ReadMapCB", `expect ", but found `+string([]byte{c})) + return + } + decoder.decoder.Decode(ptr, iter) + c = iter.nextToken() + if c != '"' { + iter.ReportError("ReadMapCB", `expect ", but found `+string([]byte{c})) + return + } +} + +type numericMapKeyEncoder struct { + encoder ValEncoder +} + +func (encoder *numericMapKeyEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.writeByte('"') + encoder.encoder.Encode(ptr, stream) + stream.writeByte('"') +} + +func (encoder *numericMapKeyEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return false +} + +type dynamicMapKeyEncoder struct { + ctx *ctx + valType reflect2.Type +} + +func (encoder *dynamicMapKeyEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + obj := encoder.valType.UnsafeIndirect(ptr) + encoderOfMapKey(encoder.ctx, reflect2.TypeOf(obj)).Encode(reflect2.PtrOf(obj), stream) +} + +func (encoder *dynamicMapKeyEncoder) IsEmpty(ptr unsafe.Pointer) bool { + obj := encoder.valType.UnsafeIndirect(ptr) + return encoderOfMapKey(encoder.ctx, reflect2.TypeOf(obj)).IsEmpty(reflect2.PtrOf(obj)) +} + +type mapEncoder struct { + mapType *reflect2.UnsafeMapType + keyEncoder ValEncoder + elemEncoder ValEncoder +} + +func (encoder *mapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + if *(*unsafe.Pointer)(ptr) == nil { + stream.WriteNil() + return + } + stream.WriteObjectStart() + iter := encoder.mapType.UnsafeIterate(ptr) + for i := 0; iter.HasNext(); i++ { + if i != 0 { + stream.WriteMore() + } + key, elem := iter.UnsafeNext() + encoder.keyEncoder.Encode(key, stream) + if stream.indention > 0 { + stream.writeTwoBytes(byte(':'), byte(' ')) + } else { + stream.writeByte(':') + } + encoder.elemEncoder.Encode(elem, stream) + } + stream.WriteObjectEnd() +} + +func (encoder *mapEncoder) IsEmpty(ptr unsafe.Pointer) bool { + iter := encoder.mapType.UnsafeIterate(ptr) + return !iter.HasNext() +} + +type sortKeysMapEncoder struct { + mapType *reflect2.UnsafeMapType + keyEncoder ValEncoder + elemEncoder ValEncoder +} + +func (encoder *sortKeysMapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + if *(*unsafe.Pointer)(ptr) == nil { + stream.WriteNil() + return + } + stream.WriteObjectStart() + mapIter := encoder.mapType.UnsafeIterate(ptr) + subStream := stream.cfg.BorrowStream(nil) + subStream.Attachment = stream.Attachment + subIter := stream.cfg.BorrowIterator(nil) + keyValues := encodedKeyValues{} + for mapIter.HasNext() { + key, elem := mapIter.UnsafeNext() + subStreamIndex := subStream.Buffered() + encoder.keyEncoder.Encode(key, subStream) + if subStream.Error != nil && subStream.Error != io.EOF && stream.Error == nil { + stream.Error = subStream.Error + } + encodedKey := subStream.Buffer()[subStreamIndex:] + subIter.ResetBytes(encodedKey) + decodedKey := subIter.ReadString() + if stream.indention > 0 { + subStream.writeTwoBytes(byte(':'), byte(' ')) + } else { + subStream.writeByte(':') + } + encoder.elemEncoder.Encode(elem, subStream) + keyValues = append(keyValues, encodedKV{ + key: decodedKey, + keyValue: subStream.Buffer()[subStreamIndex:], + }) + } + sort.Sort(keyValues) + for i, keyValue := range keyValues { + if i != 0 { + stream.WriteMore() + } + stream.Write(keyValue.keyValue) + } + if subStream.Error != nil && stream.Error == nil { + stream.Error = subStream.Error + } + stream.WriteObjectEnd() + stream.cfg.ReturnStream(subStream) + stream.cfg.ReturnIterator(subIter) +} + +func (encoder *sortKeysMapEncoder) IsEmpty(ptr unsafe.Pointer) bool { + iter := encoder.mapType.UnsafeIterate(ptr) + return !iter.HasNext() +} + +type encodedKeyValues []encodedKV + +type encodedKV struct { + key string + keyValue []byte +} + +func (sv encodedKeyValues) Len() int { return len(sv) } +func (sv encodedKeyValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } +func (sv encodedKeyValues) Less(i, j int) bool { return sv[i].key < sv[j].key } diff --git a/vendor/github.com/json-iterator/go/reflect_marshaler.go b/vendor/github.com/json-iterator/go/reflect_marshaler.go new file mode 100644 index 00000000..3e21f375 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_marshaler.go @@ -0,0 +1,225 @@ +package jsoniter + +import ( + "encoding" + "encoding/json" + "unsafe" + + "github.com/modern-go/reflect2" +) + +var marshalerType = reflect2.TypeOfPtr((*json.Marshaler)(nil)).Elem() +var unmarshalerType = reflect2.TypeOfPtr((*json.Unmarshaler)(nil)).Elem() +var textMarshalerType = reflect2.TypeOfPtr((*encoding.TextMarshaler)(nil)).Elem() +var textUnmarshalerType = reflect2.TypeOfPtr((*encoding.TextUnmarshaler)(nil)).Elem() + +func createDecoderOfMarshaler(ctx *ctx, typ reflect2.Type) ValDecoder { + ptrType := reflect2.PtrTo(typ) + if ptrType.Implements(unmarshalerType) { + return &referenceDecoder{ + &unmarshalerDecoder{ptrType}, + } + } + if ptrType.Implements(textUnmarshalerType) { + return &referenceDecoder{ + &textUnmarshalerDecoder{ptrType}, + } + } + return nil +} + +func createEncoderOfMarshaler(ctx *ctx, typ reflect2.Type) ValEncoder { + if typ == marshalerType { + checkIsEmpty := createCheckIsEmpty(ctx, typ) + var encoder ValEncoder = &directMarshalerEncoder{ + checkIsEmpty: checkIsEmpty, + } + return encoder + } + if typ.Implements(marshalerType) { + checkIsEmpty := createCheckIsEmpty(ctx, typ) + var encoder ValEncoder = &marshalerEncoder{ + valType: typ, + checkIsEmpty: checkIsEmpty, + } + return encoder + } + ptrType := reflect2.PtrTo(typ) + if ctx.prefix != "" && ptrType.Implements(marshalerType) { + checkIsEmpty := createCheckIsEmpty(ctx, ptrType) + var encoder ValEncoder = &marshalerEncoder{ + valType: ptrType, + checkIsEmpty: checkIsEmpty, + } + return &referenceEncoder{encoder} + } + if typ == textMarshalerType { + checkIsEmpty := createCheckIsEmpty(ctx, typ) + var encoder ValEncoder = &directTextMarshalerEncoder{ + checkIsEmpty: checkIsEmpty, + stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), + } + return encoder + } + if typ.Implements(textMarshalerType) { + checkIsEmpty := createCheckIsEmpty(ctx, typ) + var encoder ValEncoder = &textMarshalerEncoder{ + valType: typ, + stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), + checkIsEmpty: checkIsEmpty, + } + return encoder + } + // if prefix is empty, the type is the root type + if ctx.prefix != "" && ptrType.Implements(textMarshalerType) { + checkIsEmpty := createCheckIsEmpty(ctx, ptrType) + var encoder ValEncoder = &textMarshalerEncoder{ + valType: ptrType, + stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")), + checkIsEmpty: checkIsEmpty, + } + return &referenceEncoder{encoder} + } + return nil +} + +type marshalerEncoder struct { + checkIsEmpty checkIsEmpty + valType reflect2.Type +} + +func (encoder *marshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + obj := encoder.valType.UnsafeIndirect(ptr) + if encoder.valType.IsNullable() && reflect2.IsNil(obj) { + stream.WriteNil() + return + } + marshaler := obj.(json.Marshaler) + bytes, err := marshaler.MarshalJSON() + if err != nil { + stream.Error = err + } else { + // html escape was already done by jsoniter + // but the extra '\n' should be trimed + l := len(bytes) + if l > 0 && bytes[l-1] == '\n' { + bytes = bytes[:l-1] + } + stream.Write(bytes) + } +} + +func (encoder *marshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.checkIsEmpty.IsEmpty(ptr) +} + +type directMarshalerEncoder struct { + checkIsEmpty checkIsEmpty +} + +func (encoder *directMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + marshaler := *(*json.Marshaler)(ptr) + if marshaler == nil { + stream.WriteNil() + return + } + bytes, err := marshaler.MarshalJSON() + if err != nil { + stream.Error = err + } else { + stream.Write(bytes) + } +} + +func (encoder *directMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.checkIsEmpty.IsEmpty(ptr) +} + +type textMarshalerEncoder struct { + valType reflect2.Type + stringEncoder ValEncoder + checkIsEmpty checkIsEmpty +} + +func (encoder *textMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + obj := encoder.valType.UnsafeIndirect(ptr) + if encoder.valType.IsNullable() && reflect2.IsNil(obj) { + stream.WriteNil() + return + } + marshaler := (obj).(encoding.TextMarshaler) + bytes, err := marshaler.MarshalText() + if err != nil { + stream.Error = err + } else { + str := string(bytes) + encoder.stringEncoder.Encode(unsafe.Pointer(&str), stream) + } +} + +func (encoder *textMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.checkIsEmpty.IsEmpty(ptr) +} + +type directTextMarshalerEncoder struct { + stringEncoder ValEncoder + checkIsEmpty checkIsEmpty +} + +func (encoder *directTextMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + marshaler := *(*encoding.TextMarshaler)(ptr) + if marshaler == nil { + stream.WriteNil() + return + } + bytes, err := marshaler.MarshalText() + if err != nil { + stream.Error = err + } else { + str := string(bytes) + encoder.stringEncoder.Encode(unsafe.Pointer(&str), stream) + } +} + +func (encoder *directTextMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.checkIsEmpty.IsEmpty(ptr) +} + +type unmarshalerDecoder struct { + valType reflect2.Type +} + +func (decoder *unmarshalerDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + valType := decoder.valType + obj := valType.UnsafeIndirect(ptr) + unmarshaler := obj.(json.Unmarshaler) + iter.nextToken() + iter.unreadByte() // skip spaces + bytes := iter.SkipAndReturnBytes() + err := unmarshaler.UnmarshalJSON(bytes) + if err != nil { + iter.ReportError("unmarshalerDecoder", err.Error()) + } +} + +type textUnmarshalerDecoder struct { + valType reflect2.Type +} + +func (decoder *textUnmarshalerDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + valType := decoder.valType + obj := valType.UnsafeIndirect(ptr) + if reflect2.IsNil(obj) { + ptrType := valType.(*reflect2.UnsafePtrType) + elemType := ptrType.Elem() + elem := elemType.UnsafeNew() + ptrType.UnsafeSet(ptr, unsafe.Pointer(&elem)) + obj = valType.UnsafeIndirect(ptr) + } + unmarshaler := (obj).(encoding.TextUnmarshaler) + str := iter.ReadString() + err := unmarshaler.UnmarshalText([]byte(str)) + if err != nil { + iter.ReportError("textUnmarshalerDecoder", err.Error()) + } +} diff --git a/vendor/github.com/json-iterator/go/reflect_native.go b/vendor/github.com/json-iterator/go/reflect_native.go new file mode 100644 index 00000000..f88722d1 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_native.go @@ -0,0 +1,453 @@ +package jsoniter + +import ( + "encoding/base64" + "reflect" + "strconv" + "unsafe" + + "github.com/modern-go/reflect2" +) + +const ptrSize = 32 << uintptr(^uintptr(0)>>63) + +func createEncoderOfNative(ctx *ctx, typ reflect2.Type) ValEncoder { + if typ.Kind() == reflect.Slice && typ.(reflect2.SliceType).Elem().Kind() == reflect.Uint8 { + sliceDecoder := decoderOfSlice(ctx, typ) + return &base64Codec{sliceDecoder: sliceDecoder} + } + typeName := typ.String() + kind := typ.Kind() + switch kind { + case reflect.String: + if typeName != "string" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*string)(nil)).Elem()) + } + return &stringCodec{} + case reflect.Int: + if typeName != "int" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*int)(nil)).Elem()) + } + if strconv.IntSize == 32 { + return &int32Codec{} + } + return &int64Codec{} + case reflect.Int8: + if typeName != "int8" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*int8)(nil)).Elem()) + } + return &int8Codec{} + case reflect.Int16: + if typeName != "int16" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*int16)(nil)).Elem()) + } + return &int16Codec{} + case reflect.Int32: + if typeName != "int32" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*int32)(nil)).Elem()) + } + return &int32Codec{} + case reflect.Int64: + if typeName != "int64" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*int64)(nil)).Elem()) + } + return &int64Codec{} + case reflect.Uint: + if typeName != "uint" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*uint)(nil)).Elem()) + } + if strconv.IntSize == 32 { + return &uint32Codec{} + } + return &uint64Codec{} + case reflect.Uint8: + if typeName != "uint8" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*uint8)(nil)).Elem()) + } + return &uint8Codec{} + case reflect.Uint16: + if typeName != "uint16" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*uint16)(nil)).Elem()) + } + return &uint16Codec{} + case reflect.Uint32: + if typeName != "uint32" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*uint32)(nil)).Elem()) + } + return &uint32Codec{} + case reflect.Uintptr: + if typeName != "uintptr" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*uintptr)(nil)).Elem()) + } + if ptrSize == 32 { + return &uint32Codec{} + } + return &uint64Codec{} + case reflect.Uint64: + if typeName != "uint64" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*uint64)(nil)).Elem()) + } + return &uint64Codec{} + case reflect.Float32: + if typeName != "float32" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*float32)(nil)).Elem()) + } + return &float32Codec{} + case reflect.Float64: + if typeName != "float64" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*float64)(nil)).Elem()) + } + return &float64Codec{} + case reflect.Bool: + if typeName != "bool" { + return encoderOfType(ctx, reflect2.TypeOfPtr((*bool)(nil)).Elem()) + } + return &boolCodec{} + } + return nil +} + +func createDecoderOfNative(ctx *ctx, typ reflect2.Type) ValDecoder { + if typ.Kind() == reflect.Slice && typ.(reflect2.SliceType).Elem().Kind() == reflect.Uint8 { + sliceDecoder := decoderOfSlice(ctx, typ) + return &base64Codec{sliceDecoder: sliceDecoder} + } + typeName := typ.String() + switch typ.Kind() { + case reflect.String: + if typeName != "string" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*string)(nil)).Elem()) + } + return &stringCodec{} + case reflect.Int: + if typeName != "int" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*int)(nil)).Elem()) + } + if strconv.IntSize == 32 { + return &int32Codec{} + } + return &int64Codec{} + case reflect.Int8: + if typeName != "int8" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*int8)(nil)).Elem()) + } + return &int8Codec{} + case reflect.Int16: + if typeName != "int16" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*int16)(nil)).Elem()) + } + return &int16Codec{} + case reflect.Int32: + if typeName != "int32" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*int32)(nil)).Elem()) + } + return &int32Codec{} + case reflect.Int64: + if typeName != "int64" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*int64)(nil)).Elem()) + } + return &int64Codec{} + case reflect.Uint: + if typeName != "uint" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*uint)(nil)).Elem()) + } + if strconv.IntSize == 32 { + return &uint32Codec{} + } + return &uint64Codec{} + case reflect.Uint8: + if typeName != "uint8" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*uint8)(nil)).Elem()) + } + return &uint8Codec{} + case reflect.Uint16: + if typeName != "uint16" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*uint16)(nil)).Elem()) + } + return &uint16Codec{} + case reflect.Uint32: + if typeName != "uint32" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*uint32)(nil)).Elem()) + } + return &uint32Codec{} + case reflect.Uintptr: + if typeName != "uintptr" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*uintptr)(nil)).Elem()) + } + if ptrSize == 32 { + return &uint32Codec{} + } + return &uint64Codec{} + case reflect.Uint64: + if typeName != "uint64" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*uint64)(nil)).Elem()) + } + return &uint64Codec{} + case reflect.Float32: + if typeName != "float32" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*float32)(nil)).Elem()) + } + return &float32Codec{} + case reflect.Float64: + if typeName != "float64" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*float64)(nil)).Elem()) + } + return &float64Codec{} + case reflect.Bool: + if typeName != "bool" { + return decoderOfType(ctx, reflect2.TypeOfPtr((*bool)(nil)).Elem()) + } + return &boolCodec{} + } + return nil +} + +type stringCodec struct { +} + +func (codec *stringCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + *((*string)(ptr)) = iter.ReadString() +} + +func (codec *stringCodec) Encode(ptr unsafe.Pointer, stream *Stream) { + str := *((*string)(ptr)) + stream.WriteString(str) +} + +func (codec *stringCodec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*string)(ptr)) == "" +} + +type int8Codec struct { +} + +func (codec *int8Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*int8)(ptr)) = iter.ReadInt8() + } +} + +func (codec *int8Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteInt8(*((*int8)(ptr))) +} + +func (codec *int8Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*int8)(ptr)) == 0 +} + +type int16Codec struct { +} + +func (codec *int16Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*int16)(ptr)) = iter.ReadInt16() + } +} + +func (codec *int16Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteInt16(*((*int16)(ptr))) +} + +func (codec *int16Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*int16)(ptr)) == 0 +} + +type int32Codec struct { +} + +func (codec *int32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*int32)(ptr)) = iter.ReadInt32() + } +} + +func (codec *int32Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteInt32(*((*int32)(ptr))) +} + +func (codec *int32Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*int32)(ptr)) == 0 +} + +type int64Codec struct { +} + +func (codec *int64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*int64)(ptr)) = iter.ReadInt64() + } +} + +func (codec *int64Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteInt64(*((*int64)(ptr))) +} + +func (codec *int64Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*int64)(ptr)) == 0 +} + +type uint8Codec struct { +} + +func (codec *uint8Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*uint8)(ptr)) = iter.ReadUint8() + } +} + +func (codec *uint8Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteUint8(*((*uint8)(ptr))) +} + +func (codec *uint8Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*uint8)(ptr)) == 0 +} + +type uint16Codec struct { +} + +func (codec *uint16Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*uint16)(ptr)) = iter.ReadUint16() + } +} + +func (codec *uint16Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteUint16(*((*uint16)(ptr))) +} + +func (codec *uint16Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*uint16)(ptr)) == 0 +} + +type uint32Codec struct { +} + +func (codec *uint32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*uint32)(ptr)) = iter.ReadUint32() + } +} + +func (codec *uint32Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteUint32(*((*uint32)(ptr))) +} + +func (codec *uint32Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*uint32)(ptr)) == 0 +} + +type uint64Codec struct { +} + +func (codec *uint64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*uint64)(ptr)) = iter.ReadUint64() + } +} + +func (codec *uint64Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteUint64(*((*uint64)(ptr))) +} + +func (codec *uint64Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*uint64)(ptr)) == 0 +} + +type float32Codec struct { +} + +func (codec *float32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*float32)(ptr)) = iter.ReadFloat32() + } +} + +func (codec *float32Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteFloat32(*((*float32)(ptr))) +} + +func (codec *float32Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*float32)(ptr)) == 0 +} + +type float64Codec struct { +} + +func (codec *float64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*float64)(ptr)) = iter.ReadFloat64() + } +} + +func (codec *float64Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteFloat64(*((*float64)(ptr))) +} + +func (codec *float64Codec) IsEmpty(ptr unsafe.Pointer) bool { + return *((*float64)(ptr)) == 0 +} + +type boolCodec struct { +} + +func (codec *boolCodec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.ReadNil() { + *((*bool)(ptr)) = iter.ReadBool() + } +} + +func (codec *boolCodec) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteBool(*((*bool)(ptr))) +} + +func (codec *boolCodec) IsEmpty(ptr unsafe.Pointer) bool { + return !(*((*bool)(ptr))) +} + +type base64Codec struct { + sliceType *reflect2.UnsafeSliceType + sliceDecoder ValDecoder +} + +func (codec *base64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) { + if iter.ReadNil() { + codec.sliceType.UnsafeSetNil(ptr) + return + } + switch iter.WhatIsNext() { + case StringValue: + src := iter.ReadString() + dst, err := base64.StdEncoding.DecodeString(src) + if err != nil { + iter.ReportError("decode base64", err.Error()) + } else { + codec.sliceType.UnsafeSet(ptr, unsafe.Pointer(&dst)) + } + case ArrayValue: + codec.sliceDecoder.Decode(ptr, iter) + default: + iter.ReportError("base64Codec", "invalid input") + } +} + +func (codec *base64Codec) Encode(ptr unsafe.Pointer, stream *Stream) { + if codec.sliceType.UnsafeIsNil(ptr) { + stream.WriteNil() + return + } + src := *((*[]byte)(ptr)) + encoding := base64.StdEncoding + stream.writeByte('"') + if len(src) != 0 { + size := encoding.EncodedLen(len(src)) + buf := make([]byte, size) + encoding.Encode(buf, src) + stream.buf = append(stream.buf, buf...) + } + stream.writeByte('"') +} + +func (codec *base64Codec) IsEmpty(ptr unsafe.Pointer) bool { + return len(*((*[]byte)(ptr))) == 0 +} diff --git a/vendor/github.com/json-iterator/go/reflect_optional.go b/vendor/github.com/json-iterator/go/reflect_optional.go new file mode 100644 index 00000000..fa71f474 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_optional.go @@ -0,0 +1,129 @@ +package jsoniter + +import ( + "github.com/modern-go/reflect2" + "unsafe" +) + +func decoderOfOptional(ctx *ctx, typ reflect2.Type) ValDecoder { + ptrType := typ.(*reflect2.UnsafePtrType) + elemType := ptrType.Elem() + decoder := decoderOfType(ctx, elemType) + return &OptionalDecoder{elemType, decoder} +} + +func encoderOfOptional(ctx *ctx, typ reflect2.Type) ValEncoder { + ptrType := typ.(*reflect2.UnsafePtrType) + elemType := ptrType.Elem() + elemEncoder := encoderOfType(ctx, elemType) + encoder := &OptionalEncoder{elemEncoder} + return encoder +} + +type OptionalDecoder struct { + ValueType reflect2.Type + ValueDecoder ValDecoder +} + +func (decoder *OptionalDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if iter.ReadNil() { + *((*unsafe.Pointer)(ptr)) = nil + } else { + if *((*unsafe.Pointer)(ptr)) == nil { + //pointer to null, we have to allocate memory to hold the value + newPtr := decoder.ValueType.UnsafeNew() + decoder.ValueDecoder.Decode(newPtr, iter) + *((*unsafe.Pointer)(ptr)) = newPtr + } else { + //reuse existing instance + decoder.ValueDecoder.Decode(*((*unsafe.Pointer)(ptr)), iter) + } + } +} + +type dereferenceDecoder struct { + // only to deference a pointer + valueType reflect2.Type + valueDecoder ValDecoder +} + +func (decoder *dereferenceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if *((*unsafe.Pointer)(ptr)) == nil { + //pointer to null, we have to allocate memory to hold the value + newPtr := decoder.valueType.UnsafeNew() + decoder.valueDecoder.Decode(newPtr, iter) + *((*unsafe.Pointer)(ptr)) = newPtr + } else { + //reuse existing instance + decoder.valueDecoder.Decode(*((*unsafe.Pointer)(ptr)), iter) + } +} + +type OptionalEncoder struct { + ValueEncoder ValEncoder +} + +func (encoder *OptionalEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + if *((*unsafe.Pointer)(ptr)) == nil { + stream.WriteNil() + } else { + encoder.ValueEncoder.Encode(*((*unsafe.Pointer)(ptr)), stream) + } +} + +func (encoder *OptionalEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return *((*unsafe.Pointer)(ptr)) == nil +} + +type dereferenceEncoder struct { + ValueEncoder ValEncoder +} + +func (encoder *dereferenceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + if *((*unsafe.Pointer)(ptr)) == nil { + stream.WriteNil() + } else { + encoder.ValueEncoder.Encode(*((*unsafe.Pointer)(ptr)), stream) + } +} + +func (encoder *dereferenceEncoder) IsEmpty(ptr unsafe.Pointer) bool { + dePtr := *((*unsafe.Pointer)(ptr)) + if dePtr == nil { + return true + } + return encoder.ValueEncoder.IsEmpty(dePtr) +} + +func (encoder *dereferenceEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool { + deReferenced := *((*unsafe.Pointer)(ptr)) + if deReferenced == nil { + return true + } + isEmbeddedPtrNil, converted := encoder.ValueEncoder.(IsEmbeddedPtrNil) + if !converted { + return false + } + fieldPtr := unsafe.Pointer(deReferenced) + return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr) +} + +type referenceEncoder struct { + encoder ValEncoder +} + +func (encoder *referenceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + encoder.encoder.Encode(unsafe.Pointer(&ptr), stream) +} + +func (encoder *referenceEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.encoder.IsEmpty(unsafe.Pointer(&ptr)) +} + +type referenceDecoder struct { + decoder ValDecoder +} + +func (decoder *referenceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + decoder.decoder.Decode(unsafe.Pointer(&ptr), iter) +} diff --git a/vendor/github.com/json-iterator/go/reflect_slice.go b/vendor/github.com/json-iterator/go/reflect_slice.go new file mode 100644 index 00000000..9441d79d --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_slice.go @@ -0,0 +1,99 @@ +package jsoniter + +import ( + "fmt" + "github.com/modern-go/reflect2" + "io" + "unsafe" +) + +func decoderOfSlice(ctx *ctx, typ reflect2.Type) ValDecoder { + sliceType := typ.(*reflect2.UnsafeSliceType) + decoder := decoderOfType(ctx.append("[sliceElem]"), sliceType.Elem()) + return &sliceDecoder{sliceType, decoder} +} + +func encoderOfSlice(ctx *ctx, typ reflect2.Type) ValEncoder { + sliceType := typ.(*reflect2.UnsafeSliceType) + encoder := encoderOfType(ctx.append("[sliceElem]"), sliceType.Elem()) + return &sliceEncoder{sliceType, encoder} +} + +type sliceEncoder struct { + sliceType *reflect2.UnsafeSliceType + elemEncoder ValEncoder +} + +func (encoder *sliceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + if encoder.sliceType.UnsafeIsNil(ptr) { + stream.WriteNil() + return + } + length := encoder.sliceType.UnsafeLengthOf(ptr) + if length == 0 { + stream.WriteEmptyArray() + return + } + stream.WriteArrayStart() + encoder.elemEncoder.Encode(encoder.sliceType.UnsafeGetIndex(ptr, 0), stream) + for i := 1; i < length; i++ { + stream.WriteMore() + elemPtr := encoder.sliceType.UnsafeGetIndex(ptr, i) + encoder.elemEncoder.Encode(elemPtr, stream) + } + stream.WriteArrayEnd() + if stream.Error != nil && stream.Error != io.EOF { + stream.Error = fmt.Errorf("%v: %s", encoder.sliceType, stream.Error.Error()) + } +} + +func (encoder *sliceEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.sliceType.UnsafeLengthOf(ptr) == 0 +} + +type sliceDecoder struct { + sliceType *reflect2.UnsafeSliceType + elemDecoder ValDecoder +} + +func (decoder *sliceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + decoder.doDecode(ptr, iter) + if iter.Error != nil && iter.Error != io.EOF { + iter.Error = fmt.Errorf("%v: %s", decoder.sliceType, iter.Error.Error()) + } +} + +func (decoder *sliceDecoder) doDecode(ptr unsafe.Pointer, iter *Iterator) { + c := iter.nextToken() + sliceType := decoder.sliceType + if c == 'n' { + iter.skipThreeBytes('u', 'l', 'l') + sliceType.UnsafeSetNil(ptr) + return + } + if c != '[' { + iter.ReportError("decode slice", "expect [ or n, but found "+string([]byte{c})) + return + } + c = iter.nextToken() + if c == ']' { + sliceType.UnsafeSet(ptr, sliceType.UnsafeMakeSlice(0, 0)) + return + } + iter.unreadByte() + sliceType.UnsafeGrow(ptr, 1) + elemPtr := sliceType.UnsafeGetIndex(ptr, 0) + decoder.elemDecoder.Decode(elemPtr, iter) + length := 1 + for c = iter.nextToken(); c == ','; c = iter.nextToken() { + idx := length + length += 1 + sliceType.UnsafeGrow(ptr, length) + elemPtr = sliceType.UnsafeGetIndex(ptr, idx) + decoder.elemDecoder.Decode(elemPtr, iter) + } + if c != ']' { + iter.ReportError("decode slice", "expect ], but found "+string([]byte{c})) + return + } +} diff --git a/vendor/github.com/json-iterator/go/reflect_struct_decoder.go b/vendor/github.com/json-iterator/go/reflect_struct_decoder.go new file mode 100644 index 00000000..92ae912d --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_struct_decoder.go @@ -0,0 +1,1097 @@ +package jsoniter + +import ( + "fmt" + "io" + "strings" + "unsafe" + + "github.com/modern-go/reflect2" +) + +func decoderOfStruct(ctx *ctx, typ reflect2.Type) ValDecoder { + bindings := map[string]*Binding{} + structDescriptor := describeStruct(ctx, typ) + for _, binding := range structDescriptor.Fields { + for _, fromName := range binding.FromNames { + old := bindings[fromName] + if old == nil { + bindings[fromName] = binding + continue + } + ignoreOld, ignoreNew := resolveConflictBinding(ctx.frozenConfig, old, binding) + if ignoreOld { + delete(bindings, fromName) + } + if !ignoreNew { + bindings[fromName] = binding + } + } + } + fields := map[string]*structFieldDecoder{} + for k, binding := range bindings { + fields[k] = binding.Decoder.(*structFieldDecoder) + } + + if !ctx.caseSensitive() { + for k, binding := range bindings { + if _, found := fields[strings.ToLower(k)]; !found { + fields[strings.ToLower(k)] = binding.Decoder.(*structFieldDecoder) + } + } + } + + return createStructDecoder(ctx, typ, fields) +} + +func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structFieldDecoder) ValDecoder { + if ctx.disallowUnknownFields { + return &generalStructDecoder{typ: typ, fields: fields, disallowUnknownFields: true} + } + knownHash := map[int64]struct{}{ + 0: {}, + } + + switch len(fields) { + case 0: + return &skipObjectDecoder{typ} + case 1: + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + return &oneFieldStructDecoder{typ, fieldHash, fieldDecoder} + } + case 2: + var fieldHash1 int64 + var fieldHash2 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldHash1 == 0 { + fieldHash1 = fieldHash + fieldDecoder1 = fieldDecoder + } else { + fieldHash2 = fieldHash + fieldDecoder2 = fieldDecoder + } + } + return &twoFieldsStructDecoder{typ, fieldHash1, fieldDecoder1, fieldHash2, fieldDecoder2} + case 3: + var fieldName1 int64 + var fieldName2 int64 + var fieldName3 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + var fieldDecoder3 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldName1 == 0 { + fieldName1 = fieldHash + fieldDecoder1 = fieldDecoder + } else if fieldName2 == 0 { + fieldName2 = fieldHash + fieldDecoder2 = fieldDecoder + } else { + fieldName3 = fieldHash + fieldDecoder3 = fieldDecoder + } + } + return &threeFieldsStructDecoder{typ, + fieldName1, fieldDecoder1, + fieldName2, fieldDecoder2, + fieldName3, fieldDecoder3} + case 4: + var fieldName1 int64 + var fieldName2 int64 + var fieldName3 int64 + var fieldName4 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + var fieldDecoder3 *structFieldDecoder + var fieldDecoder4 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldName1 == 0 { + fieldName1 = fieldHash + fieldDecoder1 = fieldDecoder + } else if fieldName2 == 0 { + fieldName2 = fieldHash + fieldDecoder2 = fieldDecoder + } else if fieldName3 == 0 { + fieldName3 = fieldHash + fieldDecoder3 = fieldDecoder + } else { + fieldName4 = fieldHash + fieldDecoder4 = fieldDecoder + } + } + return &fourFieldsStructDecoder{typ, + fieldName1, fieldDecoder1, + fieldName2, fieldDecoder2, + fieldName3, fieldDecoder3, + fieldName4, fieldDecoder4} + case 5: + var fieldName1 int64 + var fieldName2 int64 + var fieldName3 int64 + var fieldName4 int64 + var fieldName5 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + var fieldDecoder3 *structFieldDecoder + var fieldDecoder4 *structFieldDecoder + var fieldDecoder5 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldName1 == 0 { + fieldName1 = fieldHash + fieldDecoder1 = fieldDecoder + } else if fieldName2 == 0 { + fieldName2 = fieldHash + fieldDecoder2 = fieldDecoder + } else if fieldName3 == 0 { + fieldName3 = fieldHash + fieldDecoder3 = fieldDecoder + } else if fieldName4 == 0 { + fieldName4 = fieldHash + fieldDecoder4 = fieldDecoder + } else { + fieldName5 = fieldHash + fieldDecoder5 = fieldDecoder + } + } + return &fiveFieldsStructDecoder{typ, + fieldName1, fieldDecoder1, + fieldName2, fieldDecoder2, + fieldName3, fieldDecoder3, + fieldName4, fieldDecoder4, + fieldName5, fieldDecoder5} + case 6: + var fieldName1 int64 + var fieldName2 int64 + var fieldName3 int64 + var fieldName4 int64 + var fieldName5 int64 + var fieldName6 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + var fieldDecoder3 *structFieldDecoder + var fieldDecoder4 *structFieldDecoder + var fieldDecoder5 *structFieldDecoder + var fieldDecoder6 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldName1 == 0 { + fieldName1 = fieldHash + fieldDecoder1 = fieldDecoder + } else if fieldName2 == 0 { + fieldName2 = fieldHash + fieldDecoder2 = fieldDecoder + } else if fieldName3 == 0 { + fieldName3 = fieldHash + fieldDecoder3 = fieldDecoder + } else if fieldName4 == 0 { + fieldName4 = fieldHash + fieldDecoder4 = fieldDecoder + } else if fieldName5 == 0 { + fieldName5 = fieldHash + fieldDecoder5 = fieldDecoder + } else { + fieldName6 = fieldHash + fieldDecoder6 = fieldDecoder + } + } + return &sixFieldsStructDecoder{typ, + fieldName1, fieldDecoder1, + fieldName2, fieldDecoder2, + fieldName3, fieldDecoder3, + fieldName4, fieldDecoder4, + fieldName5, fieldDecoder5, + fieldName6, fieldDecoder6} + case 7: + var fieldName1 int64 + var fieldName2 int64 + var fieldName3 int64 + var fieldName4 int64 + var fieldName5 int64 + var fieldName6 int64 + var fieldName7 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + var fieldDecoder3 *structFieldDecoder + var fieldDecoder4 *structFieldDecoder + var fieldDecoder5 *structFieldDecoder + var fieldDecoder6 *structFieldDecoder + var fieldDecoder7 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldName1 == 0 { + fieldName1 = fieldHash + fieldDecoder1 = fieldDecoder + } else if fieldName2 == 0 { + fieldName2 = fieldHash + fieldDecoder2 = fieldDecoder + } else if fieldName3 == 0 { + fieldName3 = fieldHash + fieldDecoder3 = fieldDecoder + } else if fieldName4 == 0 { + fieldName4 = fieldHash + fieldDecoder4 = fieldDecoder + } else if fieldName5 == 0 { + fieldName5 = fieldHash + fieldDecoder5 = fieldDecoder + } else if fieldName6 == 0 { + fieldName6 = fieldHash + fieldDecoder6 = fieldDecoder + } else { + fieldName7 = fieldHash + fieldDecoder7 = fieldDecoder + } + } + return &sevenFieldsStructDecoder{typ, + fieldName1, fieldDecoder1, + fieldName2, fieldDecoder2, + fieldName3, fieldDecoder3, + fieldName4, fieldDecoder4, + fieldName5, fieldDecoder5, + fieldName6, fieldDecoder6, + fieldName7, fieldDecoder7} + case 8: + var fieldName1 int64 + var fieldName2 int64 + var fieldName3 int64 + var fieldName4 int64 + var fieldName5 int64 + var fieldName6 int64 + var fieldName7 int64 + var fieldName8 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + var fieldDecoder3 *structFieldDecoder + var fieldDecoder4 *structFieldDecoder + var fieldDecoder5 *structFieldDecoder + var fieldDecoder6 *structFieldDecoder + var fieldDecoder7 *structFieldDecoder + var fieldDecoder8 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldName1 == 0 { + fieldName1 = fieldHash + fieldDecoder1 = fieldDecoder + } else if fieldName2 == 0 { + fieldName2 = fieldHash + fieldDecoder2 = fieldDecoder + } else if fieldName3 == 0 { + fieldName3 = fieldHash + fieldDecoder3 = fieldDecoder + } else if fieldName4 == 0 { + fieldName4 = fieldHash + fieldDecoder4 = fieldDecoder + } else if fieldName5 == 0 { + fieldName5 = fieldHash + fieldDecoder5 = fieldDecoder + } else if fieldName6 == 0 { + fieldName6 = fieldHash + fieldDecoder6 = fieldDecoder + } else if fieldName7 == 0 { + fieldName7 = fieldHash + fieldDecoder7 = fieldDecoder + } else { + fieldName8 = fieldHash + fieldDecoder8 = fieldDecoder + } + } + return &eightFieldsStructDecoder{typ, + fieldName1, fieldDecoder1, + fieldName2, fieldDecoder2, + fieldName3, fieldDecoder3, + fieldName4, fieldDecoder4, + fieldName5, fieldDecoder5, + fieldName6, fieldDecoder6, + fieldName7, fieldDecoder7, + fieldName8, fieldDecoder8} + case 9: + var fieldName1 int64 + var fieldName2 int64 + var fieldName3 int64 + var fieldName4 int64 + var fieldName5 int64 + var fieldName6 int64 + var fieldName7 int64 + var fieldName8 int64 + var fieldName9 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + var fieldDecoder3 *structFieldDecoder + var fieldDecoder4 *structFieldDecoder + var fieldDecoder5 *structFieldDecoder + var fieldDecoder6 *structFieldDecoder + var fieldDecoder7 *structFieldDecoder + var fieldDecoder8 *structFieldDecoder + var fieldDecoder9 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldName1 == 0 { + fieldName1 = fieldHash + fieldDecoder1 = fieldDecoder + } else if fieldName2 == 0 { + fieldName2 = fieldHash + fieldDecoder2 = fieldDecoder + } else if fieldName3 == 0 { + fieldName3 = fieldHash + fieldDecoder3 = fieldDecoder + } else if fieldName4 == 0 { + fieldName4 = fieldHash + fieldDecoder4 = fieldDecoder + } else if fieldName5 == 0 { + fieldName5 = fieldHash + fieldDecoder5 = fieldDecoder + } else if fieldName6 == 0 { + fieldName6 = fieldHash + fieldDecoder6 = fieldDecoder + } else if fieldName7 == 0 { + fieldName7 = fieldHash + fieldDecoder7 = fieldDecoder + } else if fieldName8 == 0 { + fieldName8 = fieldHash + fieldDecoder8 = fieldDecoder + } else { + fieldName9 = fieldHash + fieldDecoder9 = fieldDecoder + } + } + return &nineFieldsStructDecoder{typ, + fieldName1, fieldDecoder1, + fieldName2, fieldDecoder2, + fieldName3, fieldDecoder3, + fieldName4, fieldDecoder4, + fieldName5, fieldDecoder5, + fieldName6, fieldDecoder6, + fieldName7, fieldDecoder7, + fieldName8, fieldDecoder8, + fieldName9, fieldDecoder9} + case 10: + var fieldName1 int64 + var fieldName2 int64 + var fieldName3 int64 + var fieldName4 int64 + var fieldName5 int64 + var fieldName6 int64 + var fieldName7 int64 + var fieldName8 int64 + var fieldName9 int64 + var fieldName10 int64 + var fieldDecoder1 *structFieldDecoder + var fieldDecoder2 *structFieldDecoder + var fieldDecoder3 *structFieldDecoder + var fieldDecoder4 *structFieldDecoder + var fieldDecoder5 *structFieldDecoder + var fieldDecoder6 *structFieldDecoder + var fieldDecoder7 *structFieldDecoder + var fieldDecoder8 *structFieldDecoder + var fieldDecoder9 *structFieldDecoder + var fieldDecoder10 *structFieldDecoder + for fieldName, fieldDecoder := range fields { + fieldHash := calcHash(fieldName, ctx.caseSensitive()) + _, known := knownHash[fieldHash] + if known { + return &generalStructDecoder{typ, fields, false} + } + knownHash[fieldHash] = struct{}{} + if fieldName1 == 0 { + fieldName1 = fieldHash + fieldDecoder1 = fieldDecoder + } else if fieldName2 == 0 { + fieldName2 = fieldHash + fieldDecoder2 = fieldDecoder + } else if fieldName3 == 0 { + fieldName3 = fieldHash + fieldDecoder3 = fieldDecoder + } else if fieldName4 == 0 { + fieldName4 = fieldHash + fieldDecoder4 = fieldDecoder + } else if fieldName5 == 0 { + fieldName5 = fieldHash + fieldDecoder5 = fieldDecoder + } else if fieldName6 == 0 { + fieldName6 = fieldHash + fieldDecoder6 = fieldDecoder + } else if fieldName7 == 0 { + fieldName7 = fieldHash + fieldDecoder7 = fieldDecoder + } else if fieldName8 == 0 { + fieldName8 = fieldHash + fieldDecoder8 = fieldDecoder + } else if fieldName9 == 0 { + fieldName9 = fieldHash + fieldDecoder9 = fieldDecoder + } else { + fieldName10 = fieldHash + fieldDecoder10 = fieldDecoder + } + } + return &tenFieldsStructDecoder{typ, + fieldName1, fieldDecoder1, + fieldName2, fieldDecoder2, + fieldName3, fieldDecoder3, + fieldName4, fieldDecoder4, + fieldName5, fieldDecoder5, + fieldName6, fieldDecoder6, + fieldName7, fieldDecoder7, + fieldName8, fieldDecoder8, + fieldName9, fieldDecoder9, + fieldName10, fieldDecoder10} + } + return &generalStructDecoder{typ, fields, false} +} + +type generalStructDecoder struct { + typ reflect2.Type + fields map[string]*structFieldDecoder + disallowUnknownFields bool +} + +func (decoder *generalStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + var c byte + for c = ','; c == ','; c = iter.nextToken() { + decoder.decodeOneField(ptr, iter) + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + if c != '}' { + iter.ReportError("struct Decode", `expect }, but found `+string([]byte{c})) + } + iter.decrementDepth() +} + +func (decoder *generalStructDecoder) decodeOneField(ptr unsafe.Pointer, iter *Iterator) { + var field string + var fieldDecoder *structFieldDecoder + if iter.cfg.objectFieldMustBeSimpleString { + fieldBytes := iter.ReadStringAsSlice() + field = *(*string)(unsafe.Pointer(&fieldBytes)) + fieldDecoder = decoder.fields[field] + if fieldDecoder == nil && !iter.cfg.caseSensitive { + fieldDecoder = decoder.fields[strings.ToLower(field)] + } + } else { + field = iter.ReadString() + fieldDecoder = decoder.fields[field] + if fieldDecoder == nil && !iter.cfg.caseSensitive { + fieldDecoder = decoder.fields[strings.ToLower(field)] + } + } + if fieldDecoder == nil { + if decoder.disallowUnknownFields { + msg := "found unknown field: " + field + iter.ReportError("ReadObject", msg) + } + c := iter.nextToken() + if c != ':' { + iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c})) + } + iter.Skip() + return + } + c := iter.nextToken() + if c != ':' { + iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c})) + } + fieldDecoder.Decode(ptr, iter) +} + +type skipObjectDecoder struct { + typ reflect2.Type +} + +func (decoder *skipObjectDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + valueType := iter.WhatIsNext() + if valueType != ObjectValue && valueType != NilValue { + iter.ReportError("skipObjectDecoder", "expect object or null") + return + } + iter.Skip() +} + +type oneFieldStructDecoder struct { + typ reflect2.Type + fieldHash int64 + fieldDecoder *structFieldDecoder +} + +func (decoder *oneFieldStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + if iter.readFieldHash() == decoder.fieldHash { + decoder.fieldDecoder.Decode(ptr, iter) + } else { + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type twoFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder +} + +func (decoder *twoFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type threeFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder + fieldHash3 int64 + fieldDecoder3 *structFieldDecoder +} + +func (decoder *threeFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + case decoder.fieldHash3: + decoder.fieldDecoder3.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type fourFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder + fieldHash3 int64 + fieldDecoder3 *structFieldDecoder + fieldHash4 int64 + fieldDecoder4 *structFieldDecoder +} + +func (decoder *fourFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + case decoder.fieldHash3: + decoder.fieldDecoder3.Decode(ptr, iter) + case decoder.fieldHash4: + decoder.fieldDecoder4.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type fiveFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder + fieldHash3 int64 + fieldDecoder3 *structFieldDecoder + fieldHash4 int64 + fieldDecoder4 *structFieldDecoder + fieldHash5 int64 + fieldDecoder5 *structFieldDecoder +} + +func (decoder *fiveFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + case decoder.fieldHash3: + decoder.fieldDecoder3.Decode(ptr, iter) + case decoder.fieldHash4: + decoder.fieldDecoder4.Decode(ptr, iter) + case decoder.fieldHash5: + decoder.fieldDecoder5.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type sixFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder + fieldHash3 int64 + fieldDecoder3 *structFieldDecoder + fieldHash4 int64 + fieldDecoder4 *structFieldDecoder + fieldHash5 int64 + fieldDecoder5 *structFieldDecoder + fieldHash6 int64 + fieldDecoder6 *structFieldDecoder +} + +func (decoder *sixFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + case decoder.fieldHash3: + decoder.fieldDecoder3.Decode(ptr, iter) + case decoder.fieldHash4: + decoder.fieldDecoder4.Decode(ptr, iter) + case decoder.fieldHash5: + decoder.fieldDecoder5.Decode(ptr, iter) + case decoder.fieldHash6: + decoder.fieldDecoder6.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type sevenFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder + fieldHash3 int64 + fieldDecoder3 *structFieldDecoder + fieldHash4 int64 + fieldDecoder4 *structFieldDecoder + fieldHash5 int64 + fieldDecoder5 *structFieldDecoder + fieldHash6 int64 + fieldDecoder6 *structFieldDecoder + fieldHash7 int64 + fieldDecoder7 *structFieldDecoder +} + +func (decoder *sevenFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + case decoder.fieldHash3: + decoder.fieldDecoder3.Decode(ptr, iter) + case decoder.fieldHash4: + decoder.fieldDecoder4.Decode(ptr, iter) + case decoder.fieldHash5: + decoder.fieldDecoder5.Decode(ptr, iter) + case decoder.fieldHash6: + decoder.fieldDecoder6.Decode(ptr, iter) + case decoder.fieldHash7: + decoder.fieldDecoder7.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type eightFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder + fieldHash3 int64 + fieldDecoder3 *structFieldDecoder + fieldHash4 int64 + fieldDecoder4 *structFieldDecoder + fieldHash5 int64 + fieldDecoder5 *structFieldDecoder + fieldHash6 int64 + fieldDecoder6 *structFieldDecoder + fieldHash7 int64 + fieldDecoder7 *structFieldDecoder + fieldHash8 int64 + fieldDecoder8 *structFieldDecoder +} + +func (decoder *eightFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + case decoder.fieldHash3: + decoder.fieldDecoder3.Decode(ptr, iter) + case decoder.fieldHash4: + decoder.fieldDecoder4.Decode(ptr, iter) + case decoder.fieldHash5: + decoder.fieldDecoder5.Decode(ptr, iter) + case decoder.fieldHash6: + decoder.fieldDecoder6.Decode(ptr, iter) + case decoder.fieldHash7: + decoder.fieldDecoder7.Decode(ptr, iter) + case decoder.fieldHash8: + decoder.fieldDecoder8.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type nineFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder + fieldHash3 int64 + fieldDecoder3 *structFieldDecoder + fieldHash4 int64 + fieldDecoder4 *structFieldDecoder + fieldHash5 int64 + fieldDecoder5 *structFieldDecoder + fieldHash6 int64 + fieldDecoder6 *structFieldDecoder + fieldHash7 int64 + fieldDecoder7 *structFieldDecoder + fieldHash8 int64 + fieldDecoder8 *structFieldDecoder + fieldHash9 int64 + fieldDecoder9 *structFieldDecoder +} + +func (decoder *nineFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + case decoder.fieldHash3: + decoder.fieldDecoder3.Decode(ptr, iter) + case decoder.fieldHash4: + decoder.fieldDecoder4.Decode(ptr, iter) + case decoder.fieldHash5: + decoder.fieldDecoder5.Decode(ptr, iter) + case decoder.fieldHash6: + decoder.fieldDecoder6.Decode(ptr, iter) + case decoder.fieldHash7: + decoder.fieldDecoder7.Decode(ptr, iter) + case decoder.fieldHash8: + decoder.fieldDecoder8.Decode(ptr, iter) + case decoder.fieldHash9: + decoder.fieldDecoder9.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type tenFieldsStructDecoder struct { + typ reflect2.Type + fieldHash1 int64 + fieldDecoder1 *structFieldDecoder + fieldHash2 int64 + fieldDecoder2 *structFieldDecoder + fieldHash3 int64 + fieldDecoder3 *structFieldDecoder + fieldHash4 int64 + fieldDecoder4 *structFieldDecoder + fieldHash5 int64 + fieldDecoder5 *structFieldDecoder + fieldHash6 int64 + fieldDecoder6 *structFieldDecoder + fieldHash7 int64 + fieldDecoder7 *structFieldDecoder + fieldHash8 int64 + fieldDecoder8 *structFieldDecoder + fieldHash9 int64 + fieldDecoder9 *structFieldDecoder + fieldHash10 int64 + fieldDecoder10 *structFieldDecoder +} + +func (decoder *tenFieldsStructDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if !iter.readObjectStart() { + return + } + if !iter.incrementDepth() { + return + } + for { + switch iter.readFieldHash() { + case decoder.fieldHash1: + decoder.fieldDecoder1.Decode(ptr, iter) + case decoder.fieldHash2: + decoder.fieldDecoder2.Decode(ptr, iter) + case decoder.fieldHash3: + decoder.fieldDecoder3.Decode(ptr, iter) + case decoder.fieldHash4: + decoder.fieldDecoder4.Decode(ptr, iter) + case decoder.fieldHash5: + decoder.fieldDecoder5.Decode(ptr, iter) + case decoder.fieldHash6: + decoder.fieldDecoder6.Decode(ptr, iter) + case decoder.fieldHash7: + decoder.fieldDecoder7.Decode(ptr, iter) + case decoder.fieldHash8: + decoder.fieldDecoder8.Decode(ptr, iter) + case decoder.fieldHash9: + decoder.fieldDecoder9.Decode(ptr, iter) + case decoder.fieldHash10: + decoder.fieldDecoder10.Decode(ptr, iter) + default: + iter.Skip() + } + if iter.isObjectEnd() { + break + } + } + if iter.Error != nil && iter.Error != io.EOF && len(decoder.typ.Type1().Name()) != 0 { + iter.Error = fmt.Errorf("%v.%s", decoder.typ, iter.Error.Error()) + } + iter.decrementDepth() +} + +type structFieldDecoder struct { + field reflect2.StructField + fieldDecoder ValDecoder +} + +func (decoder *structFieldDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + fieldPtr := decoder.field.UnsafeGet(ptr) + decoder.fieldDecoder.Decode(fieldPtr, iter) + if iter.Error != nil && iter.Error != io.EOF { + iter.Error = fmt.Errorf("%s: %s", decoder.field.Name(), iter.Error.Error()) + } +} + +type stringModeStringDecoder struct { + elemDecoder ValDecoder + cfg *frozenConfig +} + +func (decoder *stringModeStringDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + decoder.elemDecoder.Decode(ptr, iter) + str := *((*string)(ptr)) + tempIter := decoder.cfg.BorrowIterator([]byte(str)) + defer decoder.cfg.ReturnIterator(tempIter) + *((*string)(ptr)) = tempIter.ReadString() +} + +type stringModeNumberDecoder struct { + elemDecoder ValDecoder +} + +func (decoder *stringModeNumberDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) { + if iter.WhatIsNext() == NilValue { + decoder.elemDecoder.Decode(ptr, iter) + return + } + + c := iter.nextToken() + if c != '"' { + iter.ReportError("stringModeNumberDecoder", `expect ", but found `+string([]byte{c})) + return + } + decoder.elemDecoder.Decode(ptr, iter) + if iter.Error != nil { + return + } + c = iter.readByte() + if c != '"' { + iter.ReportError("stringModeNumberDecoder", `expect ", but found `+string([]byte{c})) + return + } +} diff --git a/vendor/github.com/json-iterator/go/reflect_struct_encoder.go b/vendor/github.com/json-iterator/go/reflect_struct_encoder.go new file mode 100644 index 00000000..152e3ef5 --- /dev/null +++ b/vendor/github.com/json-iterator/go/reflect_struct_encoder.go @@ -0,0 +1,211 @@ +package jsoniter + +import ( + "fmt" + "github.com/modern-go/reflect2" + "io" + "reflect" + "unsafe" +) + +func encoderOfStruct(ctx *ctx, typ reflect2.Type) ValEncoder { + type bindingTo struct { + binding *Binding + toName string + ignored bool + } + orderedBindings := []*bindingTo{} + structDescriptor := describeStruct(ctx, typ) + for _, binding := range structDescriptor.Fields { + for _, toName := range binding.ToNames { + new := &bindingTo{ + binding: binding, + toName: toName, + } + for _, old := range orderedBindings { + if old.toName != toName { + continue + } + old.ignored, new.ignored = resolveConflictBinding(ctx.frozenConfig, old.binding, new.binding) + } + orderedBindings = append(orderedBindings, new) + } + } + if len(orderedBindings) == 0 { + return &emptyStructEncoder{} + } + finalOrderedFields := []structFieldTo{} + for _, bindingTo := range orderedBindings { + if !bindingTo.ignored { + finalOrderedFields = append(finalOrderedFields, structFieldTo{ + encoder: bindingTo.binding.Encoder.(*structFieldEncoder), + toName: bindingTo.toName, + }) + } + } + return &structEncoder{typ, finalOrderedFields} +} + +func createCheckIsEmpty(ctx *ctx, typ reflect2.Type) checkIsEmpty { + encoder := createEncoderOfNative(ctx, typ) + if encoder != nil { + return encoder + } + kind := typ.Kind() + switch kind { + case reflect.Interface: + return &dynamicEncoder{typ} + case reflect.Struct: + return &structEncoder{typ: typ} + case reflect.Array: + return &arrayEncoder{} + case reflect.Slice: + return &sliceEncoder{} + case reflect.Map: + return encoderOfMap(ctx, typ) + case reflect.Ptr: + return &OptionalEncoder{} + default: + return &lazyErrorEncoder{err: fmt.Errorf("unsupported type: %v", typ)} + } +} + +func resolveConflictBinding(cfg *frozenConfig, old, new *Binding) (ignoreOld, ignoreNew bool) { + newTagged := new.Field.Tag().Get(cfg.getTagKey()) != "" + oldTagged := old.Field.Tag().Get(cfg.getTagKey()) != "" + if newTagged { + if oldTagged { + if len(old.levels) > len(new.levels) { + return true, false + } else if len(new.levels) > len(old.levels) { + return false, true + } else { + return true, true + } + } else { + return true, false + } + } else { + if oldTagged { + return true, false + } + if len(old.levels) > len(new.levels) { + return true, false + } else if len(new.levels) > len(old.levels) { + return false, true + } else { + return true, true + } + } +} + +type structFieldEncoder struct { + field reflect2.StructField + fieldEncoder ValEncoder + omitempty bool +} + +func (encoder *structFieldEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + fieldPtr := encoder.field.UnsafeGet(ptr) + encoder.fieldEncoder.Encode(fieldPtr, stream) + if stream.Error != nil && stream.Error != io.EOF { + stream.Error = fmt.Errorf("%s: %s", encoder.field.Name(), stream.Error.Error()) + } +} + +func (encoder *structFieldEncoder) IsEmpty(ptr unsafe.Pointer) bool { + fieldPtr := encoder.field.UnsafeGet(ptr) + return encoder.fieldEncoder.IsEmpty(fieldPtr) +} + +func (encoder *structFieldEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool { + isEmbeddedPtrNil, converted := encoder.fieldEncoder.(IsEmbeddedPtrNil) + if !converted { + return false + } + fieldPtr := encoder.field.UnsafeGet(ptr) + return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr) +} + +type IsEmbeddedPtrNil interface { + IsEmbeddedPtrNil(ptr unsafe.Pointer) bool +} + +type structEncoder struct { + typ reflect2.Type + fields []structFieldTo +} + +type structFieldTo struct { + encoder *structFieldEncoder + toName string +} + +func (encoder *structEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteObjectStart() + isNotFirst := false + for _, field := range encoder.fields { + if field.encoder.omitempty && field.encoder.IsEmpty(ptr) { + continue + } + if field.encoder.IsEmbeddedPtrNil(ptr) { + continue + } + if isNotFirst { + stream.WriteMore() + } + stream.WriteObjectField(field.toName) + field.encoder.Encode(ptr, stream) + isNotFirst = true + } + stream.WriteObjectEnd() + if stream.Error != nil && stream.Error != io.EOF { + stream.Error = fmt.Errorf("%v.%s", encoder.typ, stream.Error.Error()) + } +} + +func (encoder *structEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return false +} + +type emptyStructEncoder struct { +} + +func (encoder *emptyStructEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.WriteEmptyObject() +} + +func (encoder *emptyStructEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return false +} + +type stringModeNumberEncoder struct { + elemEncoder ValEncoder +} + +func (encoder *stringModeNumberEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + stream.writeByte('"') + encoder.elemEncoder.Encode(ptr, stream) + stream.writeByte('"') +} + +func (encoder *stringModeNumberEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.elemEncoder.IsEmpty(ptr) +} + +type stringModeStringEncoder struct { + elemEncoder ValEncoder + cfg *frozenConfig +} + +func (encoder *stringModeStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) { + tempStream := encoder.cfg.BorrowStream(nil) + tempStream.Attachment = stream.Attachment + defer encoder.cfg.ReturnStream(tempStream) + encoder.elemEncoder.Encode(ptr, tempStream) + stream.WriteString(string(tempStream.Buffer())) +} + +func (encoder *stringModeStringEncoder) IsEmpty(ptr unsafe.Pointer) bool { + return encoder.elemEncoder.IsEmpty(ptr) +} diff --git a/vendor/github.com/json-iterator/go/stream.go b/vendor/github.com/json-iterator/go/stream.go new file mode 100644 index 00000000..23d8a3ad --- /dev/null +++ b/vendor/github.com/json-iterator/go/stream.go @@ -0,0 +1,210 @@ +package jsoniter + +import ( + "io" +) + +// stream is a io.Writer like object, with JSON specific write functions. +// Error is not returned as return value, but stored as Error member on this stream instance. +type Stream struct { + cfg *frozenConfig + out io.Writer + buf []byte + Error error + indention int + Attachment interface{} // open for customized encoder +} + +// NewStream create new stream instance. +// cfg can be jsoniter.ConfigDefault. +// out can be nil if write to internal buffer. +// bufSize is the initial size for the internal buffer in bytes. +func NewStream(cfg API, out io.Writer, bufSize int) *Stream { + return &Stream{ + cfg: cfg.(*frozenConfig), + out: out, + buf: make([]byte, 0, bufSize), + Error: nil, + indention: 0, + } +} + +// Pool returns a pool can provide more stream with same configuration +func (stream *Stream) Pool() StreamPool { + return stream.cfg +} + +// Reset reuse this stream instance by assign a new writer +func (stream *Stream) Reset(out io.Writer) { + stream.out = out + stream.buf = stream.buf[:0] +} + +// Available returns how many bytes are unused in the buffer. +func (stream *Stream) Available() int { + return cap(stream.buf) - len(stream.buf) +} + +// Buffered returns the number of bytes that have been written into the current buffer. +func (stream *Stream) Buffered() int { + return len(stream.buf) +} + +// Buffer if writer is nil, use this method to take the result +func (stream *Stream) Buffer() []byte { + return stream.buf +} + +// SetBuffer allows to append to the internal buffer directly +func (stream *Stream) SetBuffer(buf []byte) { + stream.buf = buf +} + +// Write writes the contents of p into the buffer. +// It returns the number of bytes written. +// If nn < len(p), it also returns an error explaining +// why the write is short. +func (stream *Stream) Write(p []byte) (nn int, err error) { + stream.buf = append(stream.buf, p...) + if stream.out != nil { + nn, err = stream.out.Write(stream.buf) + stream.buf = stream.buf[nn:] + return + } + return len(p), nil +} + +// WriteByte writes a single byte. +func (stream *Stream) writeByte(c byte) { + stream.buf = append(stream.buf, c) +} + +func (stream *Stream) writeTwoBytes(c1 byte, c2 byte) { + stream.buf = append(stream.buf, c1, c2) +} + +func (stream *Stream) writeThreeBytes(c1 byte, c2 byte, c3 byte) { + stream.buf = append(stream.buf, c1, c2, c3) +} + +func (stream *Stream) writeFourBytes(c1 byte, c2 byte, c3 byte, c4 byte) { + stream.buf = append(stream.buf, c1, c2, c3, c4) +} + +func (stream *Stream) writeFiveBytes(c1 byte, c2 byte, c3 byte, c4 byte, c5 byte) { + stream.buf = append(stream.buf, c1, c2, c3, c4, c5) +} + +// Flush writes any buffered data to the underlying io.Writer. +func (stream *Stream) Flush() error { + if stream.out == nil { + return nil + } + if stream.Error != nil { + return stream.Error + } + _, err := stream.out.Write(stream.buf) + if err != nil { + if stream.Error == nil { + stream.Error = err + } + return err + } + stream.buf = stream.buf[:0] + return nil +} + +// WriteRaw write string out without quotes, just like []byte +func (stream *Stream) WriteRaw(s string) { + stream.buf = append(stream.buf, s...) +} + +// WriteNil write null to stream +func (stream *Stream) WriteNil() { + stream.writeFourBytes('n', 'u', 'l', 'l') +} + +// WriteTrue write true to stream +func (stream *Stream) WriteTrue() { + stream.writeFourBytes('t', 'r', 'u', 'e') +} + +// WriteFalse write false to stream +func (stream *Stream) WriteFalse() { + stream.writeFiveBytes('f', 'a', 'l', 's', 'e') +} + +// WriteBool write true or false into stream +func (stream *Stream) WriteBool(val bool) { + if val { + stream.WriteTrue() + } else { + stream.WriteFalse() + } +} + +// WriteObjectStart write { with possible indention +func (stream *Stream) WriteObjectStart() { + stream.indention += stream.cfg.indentionStep + stream.writeByte('{') + stream.writeIndention(0) +} + +// WriteObjectField write "field": with possible indention +func (stream *Stream) WriteObjectField(field string) { + stream.WriteString(field) + if stream.indention > 0 { + stream.writeTwoBytes(':', ' ') + } else { + stream.writeByte(':') + } +} + +// WriteObjectEnd write } with possible indention +func (stream *Stream) WriteObjectEnd() { + stream.writeIndention(stream.cfg.indentionStep) + stream.indention -= stream.cfg.indentionStep + stream.writeByte('}') +} + +// WriteEmptyObject write {} +func (stream *Stream) WriteEmptyObject() { + stream.writeByte('{') + stream.writeByte('}') +} + +// WriteMore write , with possible indention +func (stream *Stream) WriteMore() { + stream.writeByte(',') + stream.writeIndention(0) +} + +// WriteArrayStart write [ with possible indention +func (stream *Stream) WriteArrayStart() { + stream.indention += stream.cfg.indentionStep + stream.writeByte('[') + stream.writeIndention(0) +} + +// WriteEmptyArray write [] +func (stream *Stream) WriteEmptyArray() { + stream.writeTwoBytes('[', ']') +} + +// WriteArrayEnd write ] with possible indention +func (stream *Stream) WriteArrayEnd() { + stream.writeIndention(stream.cfg.indentionStep) + stream.indention -= stream.cfg.indentionStep + stream.writeByte(']') +} + +func (stream *Stream) writeIndention(delta int) { + if stream.indention == 0 { + return + } + stream.writeByte('\n') + toWrite := stream.indention - delta + for i := 0; i < toWrite; i++ { + stream.buf = append(stream.buf, ' ') + } +} diff --git a/vendor/github.com/json-iterator/go/stream_float.go b/vendor/github.com/json-iterator/go/stream_float.go new file mode 100644 index 00000000..826aa594 --- /dev/null +++ b/vendor/github.com/json-iterator/go/stream_float.go @@ -0,0 +1,111 @@ +package jsoniter + +import ( + "fmt" + "math" + "strconv" +) + +var pow10 []uint64 + +func init() { + pow10 = []uint64{1, 10, 100, 1000, 10000, 100000, 1000000} +} + +// WriteFloat32 write float32 to stream +func (stream *Stream) WriteFloat32(val float32) { + if math.IsInf(float64(val), 0) || math.IsNaN(float64(val)) { + stream.Error = fmt.Errorf("unsupported value: %f", val) + return + } + abs := math.Abs(float64(val)) + fmt := byte('f') + // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. + if abs != 0 { + if float32(abs) < 1e-6 || float32(abs) >= 1e21 { + fmt = 'e' + } + } + stream.buf = strconv.AppendFloat(stream.buf, float64(val), fmt, -1, 32) +} + +// WriteFloat32Lossy write float32 to stream with ONLY 6 digits precision although much much faster +func (stream *Stream) WriteFloat32Lossy(val float32) { + if math.IsInf(float64(val), 0) || math.IsNaN(float64(val)) { + stream.Error = fmt.Errorf("unsupported value: %f", val) + return + } + if val < 0 { + stream.writeByte('-') + val = -val + } + if val > 0x4ffffff { + stream.WriteFloat32(val) + return + } + precision := 6 + exp := uint64(1000000) // 6 + lval := uint64(float64(val)*float64(exp) + 0.5) + stream.WriteUint64(lval / exp) + fval := lval % exp + if fval == 0 { + return + } + stream.writeByte('.') + for p := precision - 1; p > 0 && fval < pow10[p]; p-- { + stream.writeByte('0') + } + stream.WriteUint64(fval) + for stream.buf[len(stream.buf)-1] == '0' { + stream.buf = stream.buf[:len(stream.buf)-1] + } +} + +// WriteFloat64 write float64 to stream +func (stream *Stream) WriteFloat64(val float64) { + if math.IsInf(val, 0) || math.IsNaN(val) { + stream.Error = fmt.Errorf("unsupported value: %f", val) + return + } + abs := math.Abs(val) + fmt := byte('f') + // Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right. + if abs != 0 { + if abs < 1e-6 || abs >= 1e21 { + fmt = 'e' + } + } + stream.buf = strconv.AppendFloat(stream.buf, float64(val), fmt, -1, 64) +} + +// WriteFloat64Lossy write float64 to stream with ONLY 6 digits precision although much much faster +func (stream *Stream) WriteFloat64Lossy(val float64) { + if math.IsInf(val, 0) || math.IsNaN(val) { + stream.Error = fmt.Errorf("unsupported value: %f", val) + return + } + if val < 0 { + stream.writeByte('-') + val = -val + } + if val > 0x4ffffff { + stream.WriteFloat64(val) + return + } + precision := 6 + exp := uint64(1000000) // 6 + lval := uint64(val*float64(exp) + 0.5) + stream.WriteUint64(lval / exp) + fval := lval % exp + if fval == 0 { + return + } + stream.writeByte('.') + for p := precision - 1; p > 0 && fval < pow10[p]; p-- { + stream.writeByte('0') + } + stream.WriteUint64(fval) + for stream.buf[len(stream.buf)-1] == '0' { + stream.buf = stream.buf[:len(stream.buf)-1] + } +} diff --git a/vendor/github.com/json-iterator/go/stream_int.go b/vendor/github.com/json-iterator/go/stream_int.go new file mode 100644 index 00000000..d1059ee4 --- /dev/null +++ b/vendor/github.com/json-iterator/go/stream_int.go @@ -0,0 +1,190 @@ +package jsoniter + +var digits []uint32 + +func init() { + digits = make([]uint32, 1000) + for i := uint32(0); i < 1000; i++ { + digits[i] = (((i / 100) + '0') << 16) + ((((i / 10) % 10) + '0') << 8) + i%10 + '0' + if i < 10 { + digits[i] += 2 << 24 + } else if i < 100 { + digits[i] += 1 << 24 + } + } +} + +func writeFirstBuf(space []byte, v uint32) []byte { + start := v >> 24 + if start == 0 { + space = append(space, byte(v>>16), byte(v>>8)) + } else if start == 1 { + space = append(space, byte(v>>8)) + } + space = append(space, byte(v)) + return space +} + +func writeBuf(buf []byte, v uint32) []byte { + return append(buf, byte(v>>16), byte(v>>8), byte(v)) +} + +// WriteUint8 write uint8 to stream +func (stream *Stream) WriteUint8(val uint8) { + stream.buf = writeFirstBuf(stream.buf, digits[val]) +} + +// WriteInt8 write int8 to stream +func (stream *Stream) WriteInt8(nval int8) { + var val uint8 + if nval < 0 { + val = uint8(-nval) + stream.buf = append(stream.buf, '-') + } else { + val = uint8(nval) + } + stream.buf = writeFirstBuf(stream.buf, digits[val]) +} + +// WriteUint16 write uint16 to stream +func (stream *Stream) WriteUint16(val uint16) { + q1 := val / 1000 + if q1 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[val]) + return + } + r1 := val - q1*1000 + stream.buf = writeFirstBuf(stream.buf, digits[q1]) + stream.buf = writeBuf(stream.buf, digits[r1]) + return +} + +// WriteInt16 write int16 to stream +func (stream *Stream) WriteInt16(nval int16) { + var val uint16 + if nval < 0 { + val = uint16(-nval) + stream.buf = append(stream.buf, '-') + } else { + val = uint16(nval) + } + stream.WriteUint16(val) +} + +// WriteUint32 write uint32 to stream +func (stream *Stream) WriteUint32(val uint32) { + q1 := val / 1000 + if q1 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[val]) + return + } + r1 := val - q1*1000 + q2 := q1 / 1000 + if q2 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[q1]) + stream.buf = writeBuf(stream.buf, digits[r1]) + return + } + r2 := q1 - q2*1000 + q3 := q2 / 1000 + if q3 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[q2]) + } else { + r3 := q2 - q3*1000 + stream.buf = append(stream.buf, byte(q3+'0')) + stream.buf = writeBuf(stream.buf, digits[r3]) + } + stream.buf = writeBuf(stream.buf, digits[r2]) + stream.buf = writeBuf(stream.buf, digits[r1]) +} + +// WriteInt32 write int32 to stream +func (stream *Stream) WriteInt32(nval int32) { + var val uint32 + if nval < 0 { + val = uint32(-nval) + stream.buf = append(stream.buf, '-') + } else { + val = uint32(nval) + } + stream.WriteUint32(val) +} + +// WriteUint64 write uint64 to stream +func (stream *Stream) WriteUint64(val uint64) { + q1 := val / 1000 + if q1 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[val]) + return + } + r1 := val - q1*1000 + q2 := q1 / 1000 + if q2 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[q1]) + stream.buf = writeBuf(stream.buf, digits[r1]) + return + } + r2 := q1 - q2*1000 + q3 := q2 / 1000 + if q3 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[q2]) + stream.buf = writeBuf(stream.buf, digits[r2]) + stream.buf = writeBuf(stream.buf, digits[r1]) + return + } + r3 := q2 - q3*1000 + q4 := q3 / 1000 + if q4 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[q3]) + stream.buf = writeBuf(stream.buf, digits[r3]) + stream.buf = writeBuf(stream.buf, digits[r2]) + stream.buf = writeBuf(stream.buf, digits[r1]) + return + } + r4 := q3 - q4*1000 + q5 := q4 / 1000 + if q5 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[q4]) + stream.buf = writeBuf(stream.buf, digits[r4]) + stream.buf = writeBuf(stream.buf, digits[r3]) + stream.buf = writeBuf(stream.buf, digits[r2]) + stream.buf = writeBuf(stream.buf, digits[r1]) + return + } + r5 := q4 - q5*1000 + q6 := q5 / 1000 + if q6 == 0 { + stream.buf = writeFirstBuf(stream.buf, digits[q5]) + } else { + stream.buf = writeFirstBuf(stream.buf, digits[q6]) + r6 := q5 - q6*1000 + stream.buf = writeBuf(stream.buf, digits[r6]) + } + stream.buf = writeBuf(stream.buf, digits[r5]) + stream.buf = writeBuf(stream.buf, digits[r4]) + stream.buf = writeBuf(stream.buf, digits[r3]) + stream.buf = writeBuf(stream.buf, digits[r2]) + stream.buf = writeBuf(stream.buf, digits[r1]) +} + +// WriteInt64 write int64 to stream +func (stream *Stream) WriteInt64(nval int64) { + var val uint64 + if nval < 0 { + val = uint64(-nval) + stream.buf = append(stream.buf, '-') + } else { + val = uint64(nval) + } + stream.WriteUint64(val) +} + +// WriteInt write int to stream +func (stream *Stream) WriteInt(val int) { + stream.WriteInt64(int64(val)) +} + +// WriteUint write uint to stream +func (stream *Stream) WriteUint(val uint) { + stream.WriteUint64(uint64(val)) +} diff --git a/vendor/github.com/json-iterator/go/stream_str.go b/vendor/github.com/json-iterator/go/stream_str.go new file mode 100644 index 00000000..54c2ba0b --- /dev/null +++ b/vendor/github.com/json-iterator/go/stream_str.go @@ -0,0 +1,372 @@ +package jsoniter + +import ( + "unicode/utf8" +) + +// htmlSafeSet holds the value true if the ASCII character with the given +// array position can be safely represented inside a JSON string, embedded +// inside of HTML