ollama source for Momentry Core verification
This commit is contained in:
307
x/imagegen/manifest/manifest.go
Normal file
307
x/imagegen/manifest/manifest.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ollama/ollama/envconfig"
|
||||
)
|
||||
|
||||
// ManifestLayer represents a layer in the manifest.
|
||||
type ManifestLayer struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
Digest string `json:"digest"`
|
||||
Size int64 `json:"size"`
|
||||
Name string `json:"name,omitempty"` // Path-style name: "component/tensor" or "path/to/config.json"
|
||||
}
|
||||
|
||||
// Manifest represents the manifest JSON structure.
|
||||
type Manifest struct {
|
||||
SchemaVersion int `json:"schemaVersion"`
|
||||
MediaType string `json:"mediaType"`
|
||||
Config ManifestLayer `json:"config"`
|
||||
Layers []ManifestLayer `json:"layers"`
|
||||
}
|
||||
|
||||
// ModelManifest holds a parsed manifest with helper methods.
|
||||
type ModelManifest struct {
|
||||
Manifest *Manifest
|
||||
BlobDir string
|
||||
}
|
||||
|
||||
func DefaultBlobDir() string {
|
||||
return filepath.Join(envconfig.Models(), "blobs")
|
||||
}
|
||||
|
||||
// DefaultManifestDir returns the manifest storage directory.
|
||||
// Respects OLLAMA_MODELS.
|
||||
|
||||
func DefaultManifestDir() string {
|
||||
return filepath.Join(envconfig.Models(), "manifests")
|
||||
}
|
||||
|
||||
// LoadManifest loads a manifest for the given model name.
|
||||
// Model name format: "modelname" or "modelname:tag" or "host/namespace/name:tag"
|
||||
func LoadManifest(modelName string) (*ModelManifest, error) {
|
||||
manifestPath := resolveManifestPath(modelName)
|
||||
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read manifest: %w", err)
|
||||
}
|
||||
|
||||
var manifest Manifest
|
||||
if err := json.Unmarshal(data, &manifest); err != nil {
|
||||
return nil, fmt.Errorf("parse manifest: %w", err)
|
||||
}
|
||||
|
||||
return &ModelManifest{
|
||||
Manifest: &manifest,
|
||||
BlobDir: DefaultBlobDir(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// resolveManifestPath converts a model name to a manifest file path.
|
||||
func resolveManifestPath(modelName string) string {
|
||||
// Parse model name into components
|
||||
// Default: registry.ollama.ai/library/<name>/<tag>
|
||||
host := "registry.ollama.ai"
|
||||
namespace := "library"
|
||||
name := modelName
|
||||
tag := "latest"
|
||||
|
||||
// Handle explicit tag
|
||||
if idx := strings.LastIndex(name, ":"); idx != -1 {
|
||||
tag = name[idx+1:]
|
||||
name = name[:idx]
|
||||
}
|
||||
|
||||
// Handle full path like "host/namespace/name"
|
||||
parts := strings.Split(name, "/")
|
||||
switch len(parts) {
|
||||
case 3:
|
||||
host = parts[0]
|
||||
namespace = parts[1]
|
||||
name = parts[2]
|
||||
case 2:
|
||||
namespace = parts[0]
|
||||
name = parts[1]
|
||||
}
|
||||
|
||||
return filepath.Join(DefaultManifestDir(), host, namespace, name, tag)
|
||||
}
|
||||
|
||||
// BlobPath returns the full path to a blob given its digest.
|
||||
func (m *ModelManifest) BlobPath(digest string) string {
|
||||
// Convert "sha256:abc123" to "sha256-abc123"
|
||||
blobName := strings.Replace(digest, ":", "-", 1)
|
||||
return filepath.Join(m.BlobDir, blobName)
|
||||
}
|
||||
|
||||
// GetTensorLayers returns tensor layers, optionally filtered by component.
|
||||
// If component is empty, returns all tensor layers (for LLM models).
|
||||
// If component is specified (e.g., "text_encoder", "transformer", "vae"),
|
||||
// returns only layers with that prefix.
|
||||
func (m *ModelManifest) GetTensorLayers(component string) []ManifestLayer {
|
||||
var layers []ManifestLayer
|
||||
for _, layer := range m.Manifest.Layers {
|
||||
if layer.MediaType != "application/vnd.ollama.image.tensor" {
|
||||
continue
|
||||
}
|
||||
if component == "" || strings.HasPrefix(layer.Name, component+"/") {
|
||||
layers = append(layers, layer)
|
||||
}
|
||||
}
|
||||
return layers
|
||||
}
|
||||
|
||||
// GetConfigLayer returns the config layer for a given path.
|
||||
func (m *ModelManifest) GetConfigLayer(configPath string) *ManifestLayer {
|
||||
for _, layer := range m.Manifest.Layers {
|
||||
if layer.MediaType == "application/vnd.ollama.image.json" && layer.Name == configPath {
|
||||
return &layer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadConfig reads and returns the content of a config file.
|
||||
func (m *ModelManifest) ReadConfig(configPath string) ([]byte, error) {
|
||||
layer := m.GetConfigLayer(configPath)
|
||||
if layer == nil {
|
||||
return nil, fmt.Errorf("config %q not found in manifest", configPath)
|
||||
}
|
||||
|
||||
blobPath := m.BlobPath(layer.Digest)
|
||||
return os.ReadFile(blobPath)
|
||||
}
|
||||
|
||||
// ReadConfigJSON reads and unmarshals a config file.
|
||||
func (m *ModelManifest) ReadConfigJSON(configPath string, v any) error {
|
||||
data, err := m.ReadConfig(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
// OpenBlob opens a blob for reading.
|
||||
func (m *ModelManifest) OpenBlob(digest string) (io.ReadCloser, error) {
|
||||
return os.Open(m.BlobPath(digest))
|
||||
}
|
||||
|
||||
// HasTensorLayers returns true if the manifest has any tensor layers.
|
||||
func (m *ModelManifest) HasTensorLayers() bool {
|
||||
for _, layer := range m.Manifest.Layers {
|
||||
if layer.MediaType == "application/vnd.ollama.image.tensor" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TotalTensorSize returns the total size in bytes of all tensor layers.
|
||||
func (m *ModelManifest) TotalTensorSize() int64 {
|
||||
var total int64
|
||||
for _, layer := range m.Manifest.Layers {
|
||||
if layer.MediaType == "application/vnd.ollama.image.tensor" {
|
||||
total += layer.Size
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// ModelInfo contains metadata about an image generation model.
|
||||
type ModelInfo struct {
|
||||
Architecture string
|
||||
ParameterCount int64
|
||||
Quantization string
|
||||
}
|
||||
|
||||
// GetModelInfo returns metadata about an image generation model.
|
||||
func GetModelInfo(modelName string) (*ModelInfo, error) {
|
||||
manifest, err := LoadManifest(modelName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load manifest: %w", err)
|
||||
}
|
||||
|
||||
info := &ModelInfo{}
|
||||
|
||||
// Read model_index.json for architecture, parameter count, and quantization
|
||||
if data, err := manifest.ReadConfig("model_index.json"); err == nil {
|
||||
var index struct {
|
||||
Architecture string `json:"architecture"`
|
||||
ParameterCount int64 `json:"parameter_count"`
|
||||
Quantization string `json:"quantization"`
|
||||
}
|
||||
if json.Unmarshal(data, &index) == nil {
|
||||
info.Architecture = index.Architecture
|
||||
info.ParameterCount = index.ParameterCount
|
||||
info.Quantization = index.Quantization
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: detect quantization from first tensor blob's __metadata__
|
||||
if info.Quantization == "" {
|
||||
info.Quantization = detectQuantizationFromBlobs(manifest)
|
||||
}
|
||||
if info.Quantization == "" {
|
||||
info.Quantization = "BF16"
|
||||
}
|
||||
|
||||
// Fallback: estimate parameter count if not in config
|
||||
if info.ParameterCount == 0 {
|
||||
var totalSize int64
|
||||
for _, layer := range manifest.Manifest.Layers {
|
||||
if layer.MediaType == "application/vnd.ollama.image.tensor" {
|
||||
totalSize += layer.Size
|
||||
}
|
||||
}
|
||||
// Assume BF16 (2 bytes/param) as rough estimate
|
||||
info.ParameterCount = totalSize / 2
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// detectQuantizationFromBlobs reads __metadata__ from the first tensor blob
|
||||
// to detect quantization type.
|
||||
func detectQuantizationFromBlobs(manifest *ModelManifest) string {
|
||||
for _, layer := range manifest.Manifest.Layers {
|
||||
if layer.MediaType != "application/vnd.ollama.image.tensor" {
|
||||
continue
|
||||
}
|
||||
data, err := readBlobHeader(manifest.BlobPath(layer.Digest))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var header map[string]json.RawMessage
|
||||
if json.Unmarshal(data, &header) != nil {
|
||||
continue
|
||||
}
|
||||
if metaRaw, ok := header["__metadata__"]; ok {
|
||||
var meta map[string]string
|
||||
if json.Unmarshal(metaRaw, &meta) == nil {
|
||||
if qt, ok := meta["quant_type"]; ok && qt != "" {
|
||||
return strings.ToUpper(qt)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Only check the first tensor blob
|
||||
break
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ParseBlobTensorNames reads a safetensors blob and returns all "main" tensor names.
|
||||
// Filters out __metadata__, .scale, and .bias entries to return only primary weight tensors.
|
||||
func ParseBlobTensorNames(path string) ([]string, error) {
|
||||
data, err := readBlobHeader(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var header map[string]json.RawMessage
|
||||
if err := json.Unmarshal(data, &header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var names []string
|
||||
for k := range header {
|
||||
if k == "__metadata__" || strings.HasSuffix(k, ".scale") || strings.HasSuffix(k, ".bias") {
|
||||
continue
|
||||
}
|
||||
names = append(names, k)
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// readBlobHeader reads the JSON header bytes from a safetensors blob file.
|
||||
func readBlobHeader(path string) ([]byte, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var headerSize uint64
|
||||
if err := binary.Read(f, binary.LittleEndian, &headerSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if headerSize > 1024*1024 {
|
||||
return nil, fmt.Errorf("header too large: %d", headerSize)
|
||||
}
|
||||
data := make([]byte, headerSize)
|
||||
if _, err := io.ReadFull(f, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
57
x/imagegen/manifest/manifest_test.go
Normal file
57
x/imagegen/manifest/manifest_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTotalTensorSize(t *testing.T) {
|
||||
m := &ModelManifest{
|
||||
Manifest: &Manifest{
|
||||
Layers: []ManifestLayer{
|
||||
{MediaType: "application/vnd.ollama.image.tensor", Size: 1000},
|
||||
{MediaType: "application/vnd.ollama.image.tensor", Size: 2000},
|
||||
{MediaType: "application/vnd.ollama.image.json", Size: 500}, // not a tensor
|
||||
{MediaType: "application/vnd.ollama.image.tensor", Size: 3000},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got := m.TotalTensorSize()
|
||||
want := int64(6000)
|
||||
if got != want {
|
||||
t.Errorf("TotalTensorSize() = %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTotalTensorSizeEmpty(t *testing.T) {
|
||||
m := &ModelManifest{
|
||||
Manifest: &Manifest{
|
||||
Layers: []ManifestLayer{},
|
||||
},
|
||||
}
|
||||
|
||||
if got := m.TotalTensorSize(); got != 0 {
|
||||
t.Errorf("TotalTensorSize() = %d, want 0", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestAndBlobDirsRespectOLLAMAModels(t *testing.T) {
|
||||
modelsDir := filepath.Join(t.TempDir(), "models")
|
||||
|
||||
// Simulate packaged/systemd environment
|
||||
t.Setenv("OLLAMA_MODELS", modelsDir)
|
||||
t.Setenv("HOME", "/usr/share/ollama")
|
||||
|
||||
// Manifest dir must respect OLLAMA_MODELS
|
||||
wantManifest := filepath.Join(modelsDir, "manifests")
|
||||
if got := DefaultManifestDir(); got != wantManifest {
|
||||
t.Fatalf("DefaultManifestDir() = %q, want %q", got, wantManifest)
|
||||
}
|
||||
|
||||
// Blob dir must respect OLLAMA_MODELS
|
||||
wantBlobs := filepath.Join(modelsDir, "blobs")
|
||||
if got := DefaultBlobDir(); got != wantBlobs {
|
||||
t.Fatalf("DefaultBlobDir() = %q, want %q", got, wantBlobs)
|
||||
}
|
||||
}
|
||||
296
x/imagegen/manifest/weights.go
Normal file
296
x/imagegen/manifest/weights.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ollama/ollama/x/imagegen/mlx"
|
||||
)
|
||||
|
||||
// ManifestWeights provides fast weight loading from tensor blobs.
|
||||
// Uses native mmap loading with synthetic safetensors headers for zero-copy.
|
||||
type ManifestWeights struct {
|
||||
manifest *ModelManifest
|
||||
component string
|
||||
tensors map[string]ManifestLayer // name -> layer
|
||||
cache map[string]*mlx.Array // name -> loaded array
|
||||
nativeCache []*mlx.SafetensorsFile // keep native handles alive
|
||||
quantType string // quantization type from blob metadata (e.g., "int4", "int8")
|
||||
groupSize int // quantization group size from blob metadata
|
||||
}
|
||||
|
||||
// LoadWeightsFromManifest creates a weight loader from manifest storage.
|
||||
// If component is empty, loads all tensors (for LLM models).
|
||||
// If component is specified, loads only tensors for that component and strips the prefix.
|
||||
func LoadWeightsFromManifest(manifest *ModelManifest, component string) (*ManifestWeights, error) {
|
||||
layers := manifest.GetTensorLayers(component)
|
||||
if len(layers) == 0 {
|
||||
if component == "" {
|
||||
return nil, fmt.Errorf("no tensor layers found in manifest")
|
||||
}
|
||||
return nil, fmt.Errorf("no tensor layers found for component %q", component)
|
||||
}
|
||||
|
||||
// Strip component prefix from tensor names for model loading
|
||||
// e.g., "text_encoder/model.embed_tokens.weight" -> "model.embed_tokens.weight"
|
||||
tensors := make(map[string]ManifestLayer, len(layers))
|
||||
for _, layer := range layers {
|
||||
if component == "" {
|
||||
tensors[layer.Name] = layer
|
||||
} else {
|
||||
tensorName := strings.TrimPrefix(layer.Name, component+"/")
|
||||
tensors[tensorName] = layer
|
||||
}
|
||||
}
|
||||
|
||||
return &ManifestWeights{
|
||||
manifest: manifest,
|
||||
component: component,
|
||||
tensors: tensors,
|
||||
cache: make(map[string]*mlx.Array),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Load loads all tensor blobs using native mmap (zero-copy).
|
||||
// Blobs are stored in safetensors format for native mlx_load_safetensors mmap.
|
||||
// Combined quantized blobs contain tensors keyed by name, name+".scale", and optional name+".bias"
|
||||
// with quantization metadata. Scale and bias are stored in cache as name+"_scale"
|
||||
// and name+"_qbias" for compatibility with downstream loading code.
|
||||
// Packed blobs (e.g., for expert groups) contain multiple tensors; the manifest name
|
||||
// is a group prefix and individual tensors are loaded by their actual names from the blob.
|
||||
// If dtype is non-zero, non-quantized tensors are converted to the specified dtype.
|
||||
func (mw *ManifestWeights) Load(dtype mlx.Dtype) error {
|
||||
// Track native handles to free after batch eval
|
||||
nativeHandles := make([]*mlx.SafetensorsFile, 0, len(mw.tensors))
|
||||
arrays := make([]*mlx.Array, 0, len(mw.tensors))
|
||||
|
||||
// Group tensors by digest to avoid loading the same blob multiple times
|
||||
type blobEntry struct {
|
||||
name string
|
||||
layer ManifestLayer
|
||||
}
|
||||
blobGroups := make(map[string][]blobEntry)
|
||||
for name, layer := range mw.tensors {
|
||||
blobGroups[layer.Digest] = append(blobGroups[layer.Digest], blobEntry{name, layer})
|
||||
}
|
||||
|
||||
for digest, entries := range blobGroups {
|
||||
path := mw.manifest.BlobPath(digest)
|
||||
|
||||
// Load blob as safetensors (native mmap, zero-copy)
|
||||
sf, err := mlx.LoadSafetensorsNative(path)
|
||||
if err != nil {
|
||||
for _, h := range nativeHandles {
|
||||
h.Free()
|
||||
}
|
||||
return fmt.Errorf("load %s: %w", entries[0].name, err)
|
||||
}
|
||||
nativeHandles = append(nativeHandles, sf)
|
||||
|
||||
// Read quantization metadata from blob
|
||||
if qt := sf.GetMetadata("quant_type"); qt != "" && mw.quantType == "" {
|
||||
mw.quantType = qt
|
||||
if gs := sf.GetMetadata("group_size"); gs != "" {
|
||||
mw.groupSize, _ = strconv.Atoi(gs)
|
||||
}
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
name := entry.name
|
||||
|
||||
// Try to get tensor by stripped name first, then with component prefix,
|
||||
// then fall back to "data" for legacy blobs created by older versions
|
||||
// that stored all tensors with the generic key "data".
|
||||
lookupName := name
|
||||
arr := sf.Get(lookupName)
|
||||
if arr == nil && mw.component != "" {
|
||||
lookupName = mw.component + "/" + name
|
||||
arr = sf.Get(lookupName)
|
||||
}
|
||||
if arr == nil {
|
||||
// Legacy blob format: tensor stored as "data"
|
||||
lookupName = "data"
|
||||
arr = sf.Get(lookupName)
|
||||
}
|
||||
if arr != nil {
|
||||
// Single-tensor blob or tensor found by name
|
||||
if dtype != 0 && arr.Dtype() != dtype {
|
||||
arr = mlx.AsType(arr, dtype)
|
||||
}
|
||||
arr = mlx.Contiguous(arr)
|
||||
mw.cache[name] = arr
|
||||
arrays = append(arrays, arr)
|
||||
|
||||
// Check for scale tensor
|
||||
if scale := sf.Get(lookupName + ".scale"); scale != nil {
|
||||
scale = mlx.Contiguous(scale)
|
||||
mw.cache[name+"_scale"] = scale
|
||||
arrays = append(arrays, scale)
|
||||
}
|
||||
|
||||
// Check for bias tensor
|
||||
if bias := sf.Get(lookupName + ".bias"); bias != nil {
|
||||
bias = mlx.Contiguous(bias)
|
||||
mw.cache[name+"_qbias"] = bias
|
||||
arrays = append(arrays, bias)
|
||||
}
|
||||
} else {
|
||||
// Packed blob: manifest name is a group prefix, not a tensor name.
|
||||
// Load all individual tensors from the blob.
|
||||
tensorNames, err := ParseBlobTensorNames(path)
|
||||
if err != nil {
|
||||
for _, h := range nativeHandles {
|
||||
h.Free()
|
||||
}
|
||||
return fmt.Errorf("parse packed blob for %s: %w", name, err)
|
||||
}
|
||||
|
||||
for _, tensorName := range tensorNames {
|
||||
tArr := sf.Get(tensorName)
|
||||
if tArr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if dtype != 0 && tArr.Dtype() != dtype {
|
||||
tArr = mlx.AsType(tArr, dtype)
|
||||
}
|
||||
tArr = mlx.Contiguous(tArr)
|
||||
|
||||
// Strip component prefix from blob-internal names so cache keys
|
||||
// match the stripped names used by LoadModule.
|
||||
cacheName := tensorName
|
||||
if mw.component != "" {
|
||||
cacheName = strings.TrimPrefix(tensorName, mw.component+"/")
|
||||
}
|
||||
mw.cache[cacheName] = tArr
|
||||
arrays = append(arrays, tArr)
|
||||
|
||||
// Check for scale tensor
|
||||
if scale := sf.Get(tensorName + ".scale"); scale != nil {
|
||||
scale = mlx.Contiguous(scale)
|
||||
mw.cache[cacheName+"_scale"] = scale
|
||||
arrays = append(arrays, scale)
|
||||
}
|
||||
|
||||
// Check for bias tensor
|
||||
if bias := sf.Get(tensorName + ".bias"); bias != nil {
|
||||
bias = mlx.Contiguous(bias)
|
||||
mw.cache[cacheName+"_qbias"] = bias
|
||||
arrays = append(arrays, bias)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Batch evaluate all tensors at once (much faster than one at a time)
|
||||
mlx.Eval(arrays...)
|
||||
|
||||
// Now safe to free all native handles
|
||||
for _, sf := range nativeHandles {
|
||||
sf.Free()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTensor returns a tensor from cache. Call Load() first.
|
||||
func (mw *ManifestWeights) GetTensor(name string) (*mlx.Array, error) {
|
||||
if mw.cache == nil {
|
||||
return nil, fmt.Errorf("cache not initialized: call Load() first")
|
||||
}
|
||||
arr, ok := mw.cache[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("tensor %q not found", name)
|
||||
}
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
// ListTensors returns all tensor names in sorted order.
|
||||
// Includes both manifest tensor names and scale/bias entries from combined blobs.
|
||||
func (mw *ManifestWeights) ListTensors() []string {
|
||||
seen := make(map[string]bool, len(mw.tensors)+len(mw.cache))
|
||||
for name := range mw.tensors {
|
||||
seen[name] = true
|
||||
}
|
||||
// Also include cache entries (scale/bias from combined blobs)
|
||||
for name := range mw.cache {
|
||||
seen[name] = true
|
||||
}
|
||||
names := make([]string, 0, len(seen))
|
||||
for name := range seen {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
// HasTensor checks if a tensor exists in the manifest or cache.
|
||||
func (mw *ManifestWeights) HasTensor(name string) bool {
|
||||
if _, ok := mw.tensors[name]; ok {
|
||||
return true
|
||||
}
|
||||
// Also check cache for scale/bias entries from combined blobs
|
||||
if _, ok := mw.cache[name]; ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Quantization returns the model's quantization type.
|
||||
// Returns the quant_type from blob metadata (e.g., "int4", "int8", "nvfp4", "mxfp8").
|
||||
// Returns empty string if not quantized.
|
||||
// Falls back to model_index.json for image gen models.
|
||||
func (mw *ManifestWeights) Quantization() string {
|
||||
if mw.quantType != "" {
|
||||
return strings.ToUpper(mw.quantType)
|
||||
}
|
||||
|
||||
if mw.manifest == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Fallback: read from model_index.json (for image gen models)
|
||||
var index struct {
|
||||
Quantization string `json:"quantization"`
|
||||
}
|
||||
if err := mw.manifest.ReadConfigJSON("model_index.json", &index); err == nil && index.Quantization != "" {
|
||||
return index.Quantization
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GroupSize returns the quantization group size.
|
||||
// Returns the group_size from blob metadata.
|
||||
// Returns 0 if not specified (caller should use default based on quantization type).
|
||||
func (mw *ManifestWeights) GroupSize() int {
|
||||
if mw.groupSize > 0 {
|
||||
return mw.groupSize
|
||||
}
|
||||
|
||||
if mw.manifest == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Fallback: read from model_index.json (for image gen models)
|
||||
var index struct {
|
||||
GroupSize int `json:"group_size"`
|
||||
}
|
||||
if err := mw.manifest.ReadConfigJSON("model_index.json", &index); err == nil && index.GroupSize > 0 {
|
||||
return index.GroupSize
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// ReleaseAll frees all native handles and clears the tensor cache.
|
||||
func (mw *ManifestWeights) ReleaseAll() {
|
||||
for _, sf := range mw.nativeCache {
|
||||
sf.Free()
|
||||
}
|
||||
mw.nativeCache = nil
|
||||
mw.cache = nil
|
||||
}
|
||||
Reference in New Issue
Block a user