build.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package build
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io/fs"
  7. "os"
  8. "path/filepath"
  9. "github.com/ollama/ollama/x/build/internal/blobstore"
  10. "github.com/ollama/ollama/x/model"
  11. )
  12. // Errors
  13. var (
  14. ErrIncompleteRef = errors.New("unqualified ref")
  15. ErrBuildPresentInRef = errors.New("build present in ref")
  16. ErrUnsupportedModelFormat = errors.New("unsupported model format")
  17. ErrMissingFileType = errors.New("missing 'general.file_type' key")
  18. ErrNotFound = errors.New("not found")
  19. )
  20. type mediaType string
  21. // Known media types
  22. const (
  23. mediaTypeModel mediaType = "application/vnd.ollama.image.model"
  24. )
  25. type Server struct {
  26. st *blobstore.Store
  27. }
  28. // Open starts a new build server that uses dir as the base directory for all
  29. // build artifacts. If dir is empty, DefaultDir is used.
  30. //
  31. // It returns an error if the provided or default dir cannot be initialized.
  32. func Open(dir string) (*Server, error) {
  33. if dir == "" {
  34. var err error
  35. dir, err = DefaultDir()
  36. if err != nil {
  37. return nil, err
  38. }
  39. }
  40. st, err := blobstore.Open(dir)
  41. if err != nil {
  42. return nil, err
  43. }
  44. return &Server{st: st}, nil
  45. }
  46. func (s *Server) Build(ref string, f model.File) error {
  47. mp := model.ParseName(ref)
  48. if !mp.CompleteWithoutBuild() {
  49. return fmt.Errorf("%w: %q", ErrIncompleteRef, ref)
  50. }
  51. // 1. Resolve FROM
  52. // a. If it's a local file (gguf), hash it and add it to the store.
  53. // c. If it's a remote file (http), refuse.
  54. // 2. Turn other pragmas into layers, and add them to the store.
  55. // 3. Create a manifest from the layers.
  56. // 4. Store the manifest in the manifest cache
  57. // 5. Done.
  58. if f.From == "" {
  59. return &model.FileError{Pragma: "FROM", Message: "missing"}
  60. }
  61. var layers []layerJSON
  62. id, info, size, err := s.importModel(f.From)
  63. if err != nil {
  64. return err
  65. }
  66. layers = append(layers, layerJSON{
  67. ID: id,
  68. MediaType: mediaTypeModel,
  69. Size: size,
  70. })
  71. id, size, err = blobstore.PutString(s.st, f.License)
  72. if err != nil {
  73. return err
  74. }
  75. layers = append(layers, layerJSON{
  76. ID: id,
  77. MediaType: "text/plain",
  78. Size: size,
  79. })
  80. data, err := json.Marshal(manifestJSON{Layers: layers})
  81. if err != nil {
  82. return err
  83. }
  84. return s.setManifestData(
  85. mp.WithBuild(info.FileType.String()),
  86. data,
  87. )
  88. }
  89. func (s *Server) LayerFile(digest string) (string, error) {
  90. fileName := s.st.OutputFilename(blobstore.ParseID(digest))
  91. _, err := os.Stat(fileName)
  92. if errors.Is(err, fs.ErrNotExist) {
  93. return "", fmt.Errorf("%w: %q", ErrNotFound, digest)
  94. }
  95. return fileName, nil
  96. }
  97. func (s *Server) ManifestData(ref string) ([]byte, error) {
  98. data, _, err := s.resolve(model.ParseName(ref))
  99. return data, err
  100. }
  101. // WeightFile returns the absolute path to the weights file for the given model ref.
  102. func (s *Server) WeightsFile(ref string) (string, error) {
  103. m, err := s.getManifest(model.ParseName(ref))
  104. if err != nil {
  105. return "", err
  106. }
  107. for _, l := range m.Layers {
  108. if l.MediaType == mediaTypeModel {
  109. return s.st.OutputFilename(l.ID), nil
  110. }
  111. }
  112. return "", fmt.Errorf("missing weights layer for %q", ref)
  113. }
  114. // resolve returns the data for the given ref, if any.
  115. //
  116. // TODO: This should ideally return an ID, but the current on
  117. // disk layout is that the actual manifest is stored in the "ref" instead of
  118. // a pointer to a content-addressed blob. I (bmizerany) think we should
  119. // change the on-disk layout to store the manifest in a content-addressed
  120. // blob, and then have the ref point to that blob. This would simplify the
  121. // code, allow us to have integrity checks on the manifest, and clean up
  122. // this interface.
  123. func (s *Server) resolve(ref model.Name) (data []byte, fileName string, err error) {
  124. fileName, err = s.refFileName(ref)
  125. if err != nil {
  126. return nil, "", err
  127. }
  128. data, err = os.ReadFile(fileName)
  129. if errors.Is(err, fs.ErrNotExist) {
  130. return nil, "", fmt.Errorf("%w: %q", ErrNotFound, ref)
  131. }
  132. if err != nil {
  133. // do not wrap the error here, as it is likely an I/O error
  134. // and we want to preserve the absraction since we may not
  135. // be on disk later.
  136. return nil, "", fmt.Errorf("manifest read error: %v", err)
  137. }
  138. return data, fileName, nil
  139. }
  140. func (s *Server) SetManifestData(ref string, data []byte) error {
  141. return s.setManifestData(model.ParseName(ref), data)
  142. }
  143. // Set sets the data for the given ref.
  144. func (s *Server) setManifestData(mp model.Name, data []byte) error {
  145. path, err := s.refFileName(mp)
  146. if err != nil {
  147. return err
  148. }
  149. if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
  150. return err
  151. }
  152. if err := os.WriteFile(path, data, 0666); err != nil {
  153. return err
  154. }
  155. return nil
  156. }
  157. func (s *Server) refFileName(mp model.Name) (string, error) {
  158. if !mp.Complete() {
  159. return "", fmt.Errorf("ref not fully qualified: %q", mp)
  160. }
  161. return filepath.Join(s.st.Dir(), "manifests", filepath.Join(mp.Parts()...)), nil
  162. }
  163. type manifestJSON struct {
  164. // Layers is the list of layers in the manifest.
  165. Layers []layerJSON `json:"layers"`
  166. }
  167. // Layer is a layer in a model manifest.
  168. type layerJSON struct {
  169. // ID is the ID of the layer.
  170. ID blobstore.ID `json:"digest"`
  171. MediaType mediaType `json:"mediaType"`
  172. Size int64 `json:"size"`
  173. }
  174. func (s *Server) getManifest(ref model.Name) (manifestJSON, error) {
  175. data, path, err := s.resolve(ref)
  176. if err != nil {
  177. return manifestJSON{}, err
  178. }
  179. var m manifestJSON
  180. if err := json.Unmarshal(data, &m); err != nil {
  181. return manifestJSON{}, &fs.PathError{Op: "unmarshal", Path: path, Err: err}
  182. }
  183. return m, nil
  184. }