reader.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package loader
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "os"
  8. "syscall"
  9. )
  10. // ModelData represents a loaded model file
  11. type ModelData struct {
  12. Header Header
  13. Sections *SectionTable
  14. Metadata Metadata
  15. TensorIndex []TensorEntry
  16. // data is the mmap'd file
  17. data []byte
  18. file *os.File
  19. size uint64
  20. }
  21. type LoadOptions struct {
  22. // UseMmap controls whether the model file is memory-mapped.
  23. // When false, tensors are read from the file into heap allocations (RAM).
  24. UseMmap bool
  25. }
  26. // Load opens a .mak file and mmaps it (legacy behavior).
  27. func Load(path string) (*ModelData, error) {
  28. return LoadWithOptions(path, LoadOptions{UseMmap: true})
  29. }
  30. // LoadWithOptions opens a .mak file with configurable I/O strategy.
  31. func LoadWithOptions(path string, opts LoadOptions) (*ModelData, error) {
  32. f, err := os.Open(path)
  33. if err != nil {
  34. return nil, fmt.Errorf("failed to open file: %w", err)
  35. }
  36. fi, err := f.Stat()
  37. if err != nil {
  38. f.Close()
  39. return nil, fmt.Errorf("failed to stat file: %w", err)
  40. }
  41. size := fi.Size()
  42. md := &ModelData{
  43. file: f,
  44. size: uint64(size),
  45. }
  46. if opts.UseMmap {
  47. // Mmap the file
  48. data, err := syscall.Mmap(int(f.Fd()), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
  49. if err != nil {
  50. f.Close()
  51. return nil, fmt.Errorf("failed to mmap file: %w", err)
  52. }
  53. md.data = data
  54. }
  55. if err := md.parse(); err != nil {
  56. md.Close()
  57. return nil, err
  58. }
  59. return md, nil
  60. }
  61. func (md *ModelData) Close() error {
  62. if md.data != nil {
  63. syscall.Munmap(md.data)
  64. }
  65. if md.file != nil {
  66. return md.file.Close()
  67. }
  68. return nil
  69. }
  70. func (md *ModelData) parse() error {
  71. if md.data != nil {
  72. if len(md.data) < HeaderSize {
  73. return fmt.Errorf("file too small")
  74. }
  75. // Read header
  76. reader := bytes.NewReader(md.data[:HeaderSize])
  77. h, err := ReadHeader(reader)
  78. if err != nil {
  79. return err
  80. }
  81. md.Header = *h
  82. md.size = uint64(len(md.data))
  83. // Read section table
  84. sectionTableStart := HeaderSize
  85. sectionTableEnd := sectionTableStart + int(h.SectionCount)*SectionEntrySize
  86. if sectionTableEnd > len(md.data) {
  87. return fmt.Errorf("section table out of bounds")
  88. }
  89. sectionReader := bytes.NewReader(md.data[sectionTableStart:sectionTableEnd])
  90. sections, err := ReadSectionTable(sectionReader, h.SectionCount)
  91. if err != nil {
  92. return err
  93. }
  94. md.Sections = sections
  95. // Parse metadata section
  96. metaSection, err := sections.FindSection(SectionMetadata)
  97. if err != nil {
  98. return fmt.Errorf("metadata section not found: %w", err)
  99. }
  100. metaBytes := md.data[metaSection.Offset : metaSection.Offset+metaSection.Size]
  101. if err := json.Unmarshal(metaBytes, &md.Metadata); err != nil {
  102. return fmt.Errorf("failed to unmarshal metadata: %w", err)
  103. }
  104. return nil
  105. }
  106. if md.file == nil {
  107. return fmt.Errorf("no file handle")
  108. }
  109. if md.size < HeaderSize {
  110. return fmt.Errorf("file too small")
  111. }
  112. readExactAt := func(off uint64, dst []byte) error {
  113. if off+uint64(len(dst)) > md.size {
  114. return fmt.Errorf("read out of bounds: off=%d size=%d file=%d", off, len(dst), md.size)
  115. }
  116. n, err := md.file.ReadAt(dst, int64(off))
  117. if n != len(dst) {
  118. if err == nil {
  119. err = io.ErrUnexpectedEOF
  120. }
  121. return err
  122. }
  123. return nil
  124. }
  125. // Read header
  126. hdr := make([]byte, HeaderSize)
  127. if err := readExactAt(0, hdr); err != nil {
  128. return fmt.Errorf("read header: %w", err)
  129. }
  130. h, err := ReadHeader(bytes.NewReader(hdr))
  131. if err != nil {
  132. return err
  133. }
  134. md.Header = *h
  135. // Read section table
  136. stSize := uint64(h.SectionCount) * uint64(SectionEntrySize)
  137. st := make([]byte, stSize)
  138. if err := readExactAt(uint64(HeaderSize), st); err != nil {
  139. return fmt.Errorf("read section table: %w", err)
  140. }
  141. sections, err := ReadSectionTable(bytes.NewReader(st), h.SectionCount)
  142. if err != nil {
  143. return err
  144. }
  145. md.Sections = sections
  146. // Parse metadata section
  147. metaSection, err := sections.FindSection(SectionMetadata)
  148. if err != nil {
  149. return fmt.Errorf("metadata section not found: %w", err)
  150. }
  151. if metaSection.Offset+metaSection.Size > md.size {
  152. return fmt.Errorf("metadata section out of bounds")
  153. }
  154. metaBytes := make([]byte, metaSection.Size)
  155. if err := readExactAt(metaSection.Offset, metaBytes); err != nil {
  156. return fmt.Errorf("read metadata section: %w", err)
  157. }
  158. if err := json.Unmarshal(metaBytes, &md.Metadata); err != nil {
  159. return fmt.Errorf("failed to unmarshal metadata: %w", err)
  160. }
  161. return nil
  162. }
  163. // GetTensorData returns the byte slice for a named tensor
  164. func (md *ModelData) GetTensorData(name string) ([]byte, error) {
  165. // Find tensor data section for offset calculation
  166. dataSection, err := md.Sections.FindSection(SectionTensorData)
  167. if err != nil {
  168. return nil, fmt.Errorf("tensor data section not found: %w", err)
  169. }
  170. // Check tensors in metadata
  171. if info, ok := md.Metadata.Tensors[name]; ok {
  172. // info.Offset is relative to tensor data section
  173. absOffset := dataSection.Offset + info.Offset
  174. if absOffset+info.Size > md.size {
  175. return nil, fmt.Errorf("tensor %s data out of bounds", name)
  176. }
  177. if md.data != nil {
  178. return md.data[absOffset : absOffset+info.Size], nil
  179. }
  180. if info.Size > uint64(int(^uint(0)>>1)) {
  181. return nil, fmt.Errorf("tensor %s too large: %d bytes", name, info.Size)
  182. }
  183. buf := make([]byte, info.Size)
  184. n, err := md.file.ReadAt(buf, int64(absOffset))
  185. if n != len(buf) {
  186. if err == nil {
  187. err = io.ErrUnexpectedEOF
  188. }
  189. return nil, fmt.Errorf("read tensor %s: %w", name, err)
  190. }
  191. return buf, nil
  192. }
  193. // Check tensor index
  194. for _, entry := range md.TensorIndex {
  195. if entry.Name == name {
  196. // Find tensor data section
  197. dataSection, err := md.Sections.FindSection(SectionTensorData)
  198. if err != nil {
  199. return nil, err
  200. }
  201. absOffset := dataSection.Offset + entry.Offset
  202. if absOffset+entry.Size > md.size {
  203. return nil, fmt.Errorf("tensor %s data out of bounds", name)
  204. }
  205. if md.data != nil {
  206. return md.data[absOffset : absOffset+entry.Size], nil
  207. }
  208. if entry.Size > uint64(int(^uint(0)>>1)) {
  209. return nil, fmt.Errorf("tensor %s too large: %d bytes", name, entry.Size)
  210. }
  211. buf := make([]byte, entry.Size)
  212. n, err := md.file.ReadAt(buf, int64(absOffset))
  213. if n != len(buf) {
  214. if err == nil {
  215. err = io.ErrUnexpectedEOF
  216. }
  217. return nil, fmt.Errorf("read tensor %s: %w", name, err)
  218. }
  219. return buf, nil
  220. }
  221. }
  222. return nil, fmt.Errorf("tensor not found: %s", name)
  223. }
  224. // GetTokenizerData returns the embedded tokenizer JSON data if present
  225. func (md *ModelData) GetTokenizerData() ([]byte, error) {
  226. section, err := md.Sections.FindSection(SectionTokenizer)
  227. if err != nil {
  228. return nil, err // Section not found
  229. }
  230. if section.Offset+section.Size > md.size {
  231. return nil, fmt.Errorf("tokenizer data out of bounds")
  232. }
  233. if md.data != nil {
  234. return md.data[section.Offset : section.Offset+section.Size], nil
  235. }
  236. if section.Size > uint64(int(^uint(0)>>1)) {
  237. return nil, fmt.Errorf("tokenizer section too large: %d bytes", section.Size)
  238. }
  239. buf := make([]byte, section.Size)
  240. n, err := md.file.ReadAt(buf, int64(section.Offset))
  241. if n != len(buf) {
  242. if err == nil {
  243. err = io.ErrUnexpectedEOF
  244. }
  245. return nil, fmt.Errorf("read tokenizer: %w", err)
  246. }
  247. return buf, nil
  248. }