convert.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package convert
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "io/fs"
  8. "log/slog"
  9. "github.com/ollama/ollama/llm"
  10. )
  11. type Parameters struct {
  12. Architectures []string `json:"architectures"`
  13. VocabSize uint32 `json:"vocab_size"`
  14. }
  15. func (Parameters) KV(t *Tokenizer) llm.KV {
  16. kv := llm.KV{
  17. "general.file_type": uint32(1),
  18. "general.quantization_version": uint32(2),
  19. "tokenizer.ggml.pre": t.Pre,
  20. "tokenizer.ggml.model": t.Vocabulary.Model,
  21. "tokenizer.ggml.tokens": t.Vocabulary.Tokens,
  22. "tokenizer.ggml.scores": t.Vocabulary.Scores,
  23. "tokenizer.ggml.token_type": t.Vocabulary.Types,
  24. }
  25. if t.Template != "" {
  26. kv["tokenizer.chat_template"] = t.Template
  27. }
  28. for _, sv := range t.SpecialVocabulary {
  29. kv[fmt.Sprintf("tokenizer.ggml.%s_token_id", sv.Key())] = uint32(sv.ID)
  30. kv[fmt.Sprintf("tokenizer.ggml.add_%s_token", sv.Key())] = sv.AddToken
  31. }
  32. return kv
  33. }
  34. func (Parameters) specialTokenTypes() []string {
  35. return []string{
  36. "bos", "eos", "unk", "sep", "pad", "cls", "mask",
  37. }
  38. }
  39. func (Parameters) writeFile(ws io.WriteSeeker, kv llm.KV, ts []llm.Tensor) error {
  40. return llm.WriteGGUF(ws, kv, ts)
  41. }
  42. type Converter interface {
  43. // KV maps parameters to LLM key-values
  44. KV(*Tokenizer) llm.KV
  45. // Tensors maps input tensors to LLM tensors. Model specific modifications can be done here.
  46. Tensors([]Tensor) []llm.Tensor
  47. // tensorName returns the LLM tensor name for a specific input name
  48. tensorName(string) string
  49. // specialTokenTypes returns any special token types the model uses
  50. specialTokenTypes() []string
  51. writeFile(io.WriteSeeker, llm.KV, []llm.Tensor) error
  52. }
  53. // Convert writes an Ollama compatible model to the provided io.WriteSeeker based on configurations
  54. // and files it finds in the input path.
  55. // Supported input model formats include safetensors.
  56. // Supported input tokenizers files include tokenizer.json (preferred) and tokenizer.model.
  57. func Convert(fsys fs.FS, ws io.WriteSeeker) error {
  58. bts, err := fs.ReadFile(fsys, "config.json")
  59. if err != nil {
  60. return err
  61. }
  62. var p Parameters
  63. if err := json.Unmarshal(bts, &p); err != nil {
  64. return err
  65. }
  66. if len(p.Architectures) < 1 {
  67. return errors.New("unknown architecture")
  68. }
  69. var conv Converter
  70. switch p.Architectures[0] {
  71. case "LlamaForCausalLM", "MistralForCausalLM":
  72. conv = &llama{}
  73. case "MixtralForCausalLM":
  74. conv = &mixtral{}
  75. case "GemmaForCausalLM":
  76. conv = &gemma{}
  77. default:
  78. return errors.New("unsupported architecture")
  79. }
  80. if err := json.Unmarshal(bts, conv); err != nil {
  81. return err
  82. }
  83. t, err := parseTokenizer(fsys, conv.specialTokenTypes())
  84. if err != nil {
  85. return err
  86. }
  87. if vocabSize := int(p.VocabSize); vocabSize > len(t.Vocabulary.Tokens) {
  88. slog.Warn("vocabulary is smaller than expected, padding with dummy tokens", "expect", p.VocabSize, "actual", len(t.Vocabulary.Tokens))
  89. for i := range vocabSize - len(t.Vocabulary.Tokens) {
  90. t.Vocabulary.Tokens = append(t.Vocabulary.Tokens, fmt.Sprintf("[PAD%d]", i))
  91. t.Vocabulary.Scores = append(t.Vocabulary.Scores, -1)
  92. t.Vocabulary.Types = append(t.Vocabulary.Types, tokenTypeUserDefined)
  93. }
  94. } else {
  95. slog.Debug("vocabulary", "size", len(t.Vocabulary.Tokens))
  96. }
  97. ts, err := parseTensors(fsys)
  98. if err != nil {
  99. return err
  100. }
  101. return conv.writeFile(ws, conv.KV(t), conv.Tensors(ts))
  102. }