Downloading the 300 MB model restarted from zero after any failure and hung forever on a stalled connection. Download into a .partial file that survives interruptions, resume it with a Range request (falling back to a fresh download when the server cannot), skip files already verified, and abort when nothing arrives for 30 s. A model directory only counts as installed when every file has its pinned size, so a truncated model is offered for download again instead of failing to load. The model load held the translation mutex, so Cancel did nothing for the seconds it takes to read 300 MB. Load without holding the lock, make marian.Load context-aware, and let CancelTranslation wait for the task to wind down so a new one can start at once. The idle release timer re-arms when it fires while a download holds the task slot, instead of never freeing the model.
142 lines
3.5 KiB
Go
142 lines
3.5 KiB
Go
package marian
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"unsafe"
|
|
)
|
|
|
|
// tensor is a weight matrix or vector read from a safetensors file, in
|
|
// row-major order.
|
|
type tensor struct {
|
|
shape []int
|
|
data []float32
|
|
}
|
|
|
|
type tensorInfo struct {
|
|
DType string `json:"dtype"`
|
|
Shape []int `json:"shape"`
|
|
Offsets [2]int64 `json:"data_offsets"`
|
|
}
|
|
|
|
// readSafetensors loads every tensor of a safetensors file (an 8-byte
|
|
// little-endian header length, a JSON header, then the raw data) as
|
|
// float32. F32 tensors are read straight into their slice: the file is
|
|
// large and a second copy would double the memory needed to load it.
|
|
func readSafetensors(ctx context.Context, path string) (map[string]tensor, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
var size uint64
|
|
if err := binary.Read(f, binary.LittleEndian, &size); err != nil {
|
|
return nil, fmt.Errorf("reading header size: %w", err)
|
|
}
|
|
if size > 100<<20 {
|
|
return nil, fmt.Errorf("header too large (%d bytes)", size)
|
|
}
|
|
header := make([]byte, size)
|
|
if _, err := io.ReadFull(f, header); err != nil {
|
|
return nil, fmt.Errorf("reading header: %w", err)
|
|
}
|
|
var infos map[string]json.RawMessage
|
|
if err := json.Unmarshal(header, &infos); err != nil {
|
|
return nil, fmt.Errorf("parsing header: %w", err)
|
|
}
|
|
base := int64(8 + size)
|
|
out := make(map[string]tensor, len(infos))
|
|
for name, raw := range infos {
|
|
if name == "__metadata__" {
|
|
continue
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
var info tensorInfo
|
|
if err := json.Unmarshal(raw, &info); err != nil {
|
|
return nil, fmt.Errorf("tensor %s: %w", name, err)
|
|
}
|
|
n := 1
|
|
for _, d := range info.Shape {
|
|
n *= d
|
|
}
|
|
t, err := readTensor(f, base, info, n)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("tensor %s: %w", name, err)
|
|
}
|
|
out[name] = tensor{shape: info.Shape, data: t}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func readTensor(f *os.File, base int64, info tensorInfo, n int) ([]float32, error) {
|
|
start, end := info.Offsets[0], info.Offsets[1]
|
|
width := map[string]int64{"F32": 4, "F16": 2, "BF16": 2}[info.DType]
|
|
if width == 0 {
|
|
return nil, fmt.Errorf("unsupported type %s", info.DType)
|
|
}
|
|
if end-start != int64(n)*width {
|
|
return nil, fmt.Errorf("size %d does not match shape %v", end-start, info.Shape)
|
|
}
|
|
data := make([]float32, n)
|
|
if n == 0 {
|
|
return data, nil
|
|
}
|
|
if info.DType == "F32" {
|
|
buf := unsafe.Slice((*byte)(unsafe.Pointer(&data[0])), n*4)
|
|
if _, err := f.ReadAt(buf, base+start); err != nil {
|
|
return nil, err
|
|
}
|
|
if !littleEndian {
|
|
for i := range data {
|
|
data[i] = math.Float32frombits(binary.LittleEndian.Uint32(buf[i*4:]))
|
|
}
|
|
}
|
|
return data, nil
|
|
}
|
|
buf := make([]byte, n*2)
|
|
if _, err := f.ReadAt(buf, base+start); err != nil {
|
|
return nil, err
|
|
}
|
|
for i := range data {
|
|
h := binary.LittleEndian.Uint16(buf[i*2:])
|
|
if info.DType == "BF16" {
|
|
data[i] = math.Float32frombits(uint32(h) << 16)
|
|
} else {
|
|
data[i] = half(h)
|
|
}
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
var littleEndian = func() bool {
|
|
x := uint16(1)
|
|
return *(*byte)(unsafe.Pointer(&x)) == 1
|
|
}()
|
|
|
|
// half converts an IEEE 754 half-precision float.
|
|
func half(h uint16) float32 {
|
|
sign := uint32(h>>15) << 31
|
|
exp := uint32(h>>10) & 0x1f
|
|
frac := uint32(h) & 0x3ff
|
|
switch {
|
|
case exp == 0 && frac == 0:
|
|
return math.Float32frombits(sign)
|
|
case exp == 0: // subnormal
|
|
v := float32(frac) / 1024 / 16384
|
|
if sign != 0 {
|
|
return -v
|
|
}
|
|
return v
|
|
case exp == 0x1f:
|
|
return math.Float32frombits(sign | 0xff<<23 | frac<<13)
|
|
}
|
|
return math.Float32frombits(sign | (exp+112)<<23 | frac<<13)
|
|
}
|