Skip to content

Commit f8dabcd

Browse files
authored
feat: align with the latest model spec (#67)
Signed-off-by: chlins <[email protected]>
1 parent cd465f1 commit f8dabcd

File tree

18 files changed

+171
-188
lines changed

18 files changed

+171
-188
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/CloudNativeAI/modctl
33
go 1.23.3
44

55
require (
6-
github.com/CloudNativeAI/model-spec v0.0.0-20241121031550-59ad02d4a225
6+
github.com/CloudNativeAI/model-spec v0.0.0-20250212030633-7aabe2b8eadc
77
github.com/distribution/distribution/v3 v3.0.0-rc.2
88
github.com/distribution/reference v0.6.0
99
github.com/dustin/go-humanize v1.0.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/CloudNativeAI/model-spec v0.0.0-20241121031550-59ad02d4a225 h1:/wakwn9KDTKnVSlg+H12XxsGSVSNs0XK/j9UjUw3vUs=
22
github.com/CloudNativeAI/model-spec v0.0.0-20241121031550-59ad02d4a225/go.mod h1:/wGtOmyHRaxmWGWguc1oh7pcYbWyiuXG+VkAV8vmOTI=
3+
github.com/CloudNativeAI/model-spec v0.0.0-20250212030633-7aabe2b8eadc h1:EmVmYn5o4jGweOlvjsbsdeSlSpNYvWPQ2muLaQXT1L8=
4+
github.com/CloudNativeAI/model-spec v0.0.0-20250212030633-7aabe2b8eadc/go.mod h1:3U/4zubBfbUkW59ATSg41HnkYyKrKUcKFH/cVdoPQnk=
35
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
46
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
57
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=

pkg/backend/build.go

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,10 @@ import (
2121
"fmt"
2222
"os"
2323
"path/filepath"
24-
"time"
2524

2625
"github.com/CloudNativeAI/modctl/pkg/backend/build"
2726
"github.com/CloudNativeAI/modctl/pkg/backend/processor"
2827
"github.com/CloudNativeAI/modctl/pkg/modelfile"
29-
modelspec "github.com/CloudNativeAI/model-spec/specs-go/v1"
3028

3129
humanize "github.com/dustin/go-humanize"
3230
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -55,15 +53,15 @@ func (b *backend) Build(ctx context.Context, modelfilePath, workDir, target stri
5553
layers = append(layers, layerDescs...)
5654

5755
// build the image config.
58-
configDesc, err := build.BuildConfig(ctx, b.store, repo)
56+
configDesc, err := build.BuildConfig(ctx, b.store, modelfile, repo)
5957
if err != nil {
6058
return fmt.Errorf("failed to build image config: %w", err)
6159
}
6260

6361
fmt.Printf("%-15s => %s (%s)\n", "Built config", configDesc.Digest, humanize.IBytes(uint64(configDesc.Size)))
6462

6563
// build the image manifest.
66-
manifestDesc, err := build.BuildManifest(ctx, b.store, repo, tag, layers, configDesc, manifestAnnotation(modelfile))
64+
manifestDesc, err := build.BuildManifest(ctx, b.store, repo, tag, layers, configDesc, manifestAnnotation())
6765
if err != nil {
6866
return fmt.Errorf("failed to build image manifest: %w", err)
6967
}
@@ -133,38 +131,8 @@ func (b *backend) process(ctx context.Context, workDir string, repo string, proc
133131
}
134132

135133
// manifestAnnotation returns the annotations for the manifest.
136-
func manifestAnnotation(modelfile modelfile.Modelfile) map[string]string {
137-
anno := map[string]string{
138-
modelspec.AnnotationCreated: time.Now().Format(time.RFC3339),
139-
}
140-
141-
if arch := modelfile.GetArch(); arch != "" {
142-
anno[modelspec.AnnotationArchitecture] = arch
143-
}
144-
145-
if family := modelfile.GetFamily(); family != "" {
146-
anno[modelspec.AnnotationFamily] = family
147-
}
148-
149-
if name := modelfile.GetName(); name != "" {
150-
anno[modelspec.AnnotationName] = name
151-
}
152-
153-
if format := modelfile.GetFormat(); format != "" {
154-
anno[modelspec.AnnotationFormat] = format
155-
}
156-
157-
if paramsize := modelfile.GetParamsize(); paramsize != "" {
158-
anno[modelspec.AnnotationParamSize] = paramsize
159-
}
160-
161-
if precision := modelfile.GetPrecision(); precision != "" {
162-
anno[modelspec.AnnotationPrecision] = precision
163-
}
164-
165-
if quantization := modelfile.GetQuantization(); quantization != "" {
166-
anno[modelspec.AnnotationQuantization] = quantization
167-
}
168-
134+
func manifestAnnotation() map[string]string {
135+
// placeholder for future expansion of annotations.
136+
anno := map[string]string{}
169137
return anno
170138
}

pkg/backend/build/build.go

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ import (
2222
"encoding/json"
2323
"fmt"
2424
"path/filepath"
25+
"strconv"
2526
"time"
2627

2728
"github.com/CloudNativeAI/modctl/pkg/archiver"
29+
"github.com/CloudNativeAI/modctl/pkg/modelfile"
2830
"github.com/CloudNativeAI/modctl/pkg/storage"
2931
modelspec "github.com/CloudNativeAI/model-spec/specs-go/v1"
3032

@@ -33,14 +35,8 @@ import (
3335
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3436
)
3537

36-
// ModelConfig is a configuration that corresponds to the image config in the image spec.
37-
type ModelConfig struct {
38-
// Created is the time when the model image is created.
39-
Created string `json:"Created"`
40-
}
41-
4238
// BuildLayer converts the file to the image blob and push it to the storage.
43-
func BuildLayer(ctx context.Context, store storage.Storage, repo, path, workDir string) (ocispec.Descriptor, error) {
39+
func BuildLayer(ctx context.Context, store storage.Storage, mediaType, repo, path, workDir string) (ocispec.Descriptor, error) {
4440
reader, err := archiver.Tar(path)
4541
if err != nil {
4642
return ocispec.Descriptor{}, fmt.Errorf("failed to tar file: %w", err)
@@ -62,21 +58,55 @@ func BuildLayer(ctx context.Context, store storage.Storage, repo, path, workDir
6258
}
6359

6460
return ocispec.Descriptor{
65-
ArtifactType: modelspec.ArtifactTypeModelLayer,
66-
MediaType: ocispec.MediaTypeImageLayer,
67-
Digest: godigest.Digest(digest),
68-
Size: size,
61+
MediaType: mediaType,
62+
Digest: godigest.Digest(digest),
63+
Size: size,
6964
Annotations: map[string]string{
7065
modelspec.AnnotationFilepath: filePath,
7166
},
7267
}, nil
7368
}
7469

70+
// buildModelConfig builds the model config.
71+
func buildModelConfig(modelfile modelfile.Modelfile) (*modelspec.Model, error) {
72+
config := modelspec.ModelConfig{
73+
Architecture: modelfile.GetArch(),
74+
Format: modelfile.GetFormat(),
75+
Precision: modelfile.GetPrecision(),
76+
Quantization: modelfile.GetQuantization(),
77+
}
78+
// parse the parameter size.
79+
paramSize, err := strconv.ParseUint(modelfile.GetParamsize(), 10, 64)
80+
if err != nil {
81+
return nil, fmt.Errorf("failed to parse paramsize %s to uint64: %w", modelfile.GetParamsize(), err)
82+
}
83+
config.ParameterSize = paramSize
84+
85+
createdAt := time.Now()
86+
descriptor := modelspec.ModelDescriptor{
87+
CreatedAt: &createdAt,
88+
Family: modelfile.GetFamily(),
89+
Name: modelfile.GetName(),
90+
}
91+
92+
fs := modelspec.ModelFS{
93+
Type: "layers",
94+
}
95+
96+
return &modelspec.Model{
97+
Config: config,
98+
Descriptor: descriptor,
99+
ModelFS: fs,
100+
}, nil
101+
}
102+
75103
// BuildConfig builds the image config and push it to the storage.
76-
func BuildConfig(ctx context.Context, store storage.Storage, repo string) (ocispec.Descriptor, error) {
77-
config := &ModelConfig{
78-
Created: time.Now().Format(time.RFC3339Nano),
104+
func BuildConfig(ctx context.Context, store storage.Storage, modelfile modelfile.Modelfile, repo string) (ocispec.Descriptor, error) {
105+
config, err := buildModelConfig(modelfile)
106+
if err != nil {
107+
return ocispec.Descriptor{}, fmt.Errorf("failed to build model config: %w", err)
79108
}
109+
80110
configJSON, err := json.Marshal(config)
81111
if err != nil {
82112
return ocispec.Descriptor{}, fmt.Errorf("failed to marshal config: %w", err)
@@ -88,8 +118,7 @@ func BuildConfig(ctx context.Context, store storage.Storage, repo string) (ocisp
88118
}
89119

90120
return ocispec.Descriptor{
91-
// reuse the image config media type for runtime compatibility.
92-
MediaType: ocispec.MediaTypeImageConfig,
121+
MediaType: modelspec.MediaTypeModelConfig,
93122
Size: size,
94123
Digest: godigest.Digest(digest),
95124
}, nil

pkg/backend/build_test.go

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,12 @@ package backend
1818

1919
import (
2020
"testing"
21-
"time"
2221

2322
"github.com/CloudNativeAI/modctl/test/mocks/modelfile"
24-
modelspec "github.com/CloudNativeAI/model-spec/specs-go/v1"
2523

2624
"github.com/stretchr/testify/assert"
2725
)
2826

29-
func TestManifestAnnotation(t *testing.T) {
30-
modelfile := &modelfile.Modelfile{}
31-
modelfile.On("GetArch").Return("test-arch")
32-
modelfile.On("GetFamily").Return("test-family")
33-
modelfile.On("GetName").Return("test-model")
34-
modelfile.On("GetFormat").Return("test-format")
35-
modelfile.On("GetParamsize").Return("12345")
36-
modelfile.On("GetPrecision").Return("FP32")
37-
modelfile.On("GetQuantization").Return("INT8")
38-
39-
annotations := manifestAnnotation(modelfile)
40-
41-
assert.Equal(t, "test-arch", annotations[modelspec.AnnotationArchitecture])
42-
assert.Equal(t, "test-family", annotations[modelspec.AnnotationFamily])
43-
assert.Equal(t, "test-model", annotations[modelspec.AnnotationName])
44-
assert.Equal(t, "test-format", annotations[modelspec.AnnotationFormat])
45-
assert.Equal(t, "12345", annotations[modelspec.AnnotationParamSize])
46-
assert.Equal(t, "FP32", annotations[modelspec.AnnotationPrecision])
47-
assert.Equal(t, "INT8", annotations[modelspec.AnnotationQuantization])
48-
49-
createdTime, err := time.Parse(time.RFC3339, annotations[modelspec.AnnotationCreated])
50-
assert.NoError(t, err)
51-
assert.WithinDuration(t, time.Now(), createdTime, time.Minute)
52-
}
53-
5427
func TestGetProcessors(t *testing.T) {
5528
modelfile := &modelfile.Modelfile{}
5629
modelfile.On("GetConfigs").Return([]string{"config1", "config2"})

pkg/backend/inspect.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"encoding/json"
2222
"fmt"
23+
"time"
2324

2425
modelspec "github.com/CloudNativeAI/model-spec/specs-go/v1"
2526
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -33,8 +34,8 @@ type InspectedModelArtifact struct {
3334
Digest string `json:"Digest"`
3435
// Architecture is the architecture of the model.
3536
Architecture string `json:"Architecture"`
36-
// Created is the creation time of the model artifact.
37-
Created string `json:"Created"`
37+
// CreatedAt is the creation time of the model artifact.
38+
CreatedAt string `json:"CreatedAt"`
3839
// Family is the family of the model.
3940
Family string `json:"Family"`
4041
// Format is the format of the model.
@@ -79,17 +80,32 @@ func (b *backend) Inspect(ctx context.Context, target string) (*InspectedModelAr
7980
return nil, fmt.Errorf("failed to unmarshal manifest: %w", err)
8081
}
8182

83+
// fetch and parse the model config.
84+
configReader, err := b.store.PullBlob(ctx, repo, manifest.Config.Digest.String())
85+
if err != nil {
86+
return nil, fmt.Errorf("failed to pull config: %w", err)
87+
}
88+
89+
defer configReader.Close()
90+
var config modelspec.Model
91+
if err := json.NewDecoder(configReader).Decode(&config); err != nil {
92+
return nil, fmt.Errorf("failed to decode config: %w", err)
93+
}
94+
8295
inspectedModelArtifact := &InspectedModelArtifact{
8396
ID: manifest.Config.Digest.String(),
8497
Digest: digest,
85-
Architecture: manifest.Annotations[modelspec.AnnotationArchitecture],
86-
Created: manifest.Annotations[modelspec.AnnotationCreated],
87-
Family: manifest.Annotations[modelspec.AnnotationFamily],
88-
Format: manifest.Annotations[modelspec.AnnotationFormat],
89-
Name: manifest.Annotations[modelspec.AnnotationName],
90-
ParamSize: manifest.Annotations[modelspec.AnnotationParamSize],
91-
Precision: manifest.Annotations[modelspec.AnnotationPrecision],
92-
Quantization: manifest.Annotations[modelspec.AnnotationQuantization],
98+
Architecture: config.Config.Architecture,
99+
Family: config.Descriptor.Family,
100+
Format: config.Config.Format,
101+
Name: config.Descriptor.Name,
102+
ParamSize: fmt.Sprintf("%d", config.Config.ParameterSize),
103+
Precision: config.Config.Precision,
104+
Quantization: config.Config.Quantization,
105+
}
106+
107+
if config.Descriptor.CreatedAt != nil {
108+
inspectedModelArtifact.CreatedAt = config.Descriptor.CreatedAt.Format(time.RFC3339)
93109
}
94110

95111
for _, layer := range manifest.Layers {

0 commit comments

Comments
 (0)