Module: StaticEmbeddings::Safetensors
- Defined in:
- lib/static_embeddings/safetensors.rb
Constant Summary collapse
- MAX_HEADER_BYTES =
100 * 1024 * 1024
- SUPPORTED_DTYPES =
{ "F32" => 4 }.freeze
Class Method Summary collapse
- .checked_offsets(name, offsets, body_size) ⇒ Object
- .checked_tensor_bytes(name, dtype, shape) ⇒ Object
- .parse_header(data) ⇒ Object
- .read(path) ⇒ Object
- .tensor_from(data, body_offset, body_size, name, spec) ⇒ Object
- .write(path, name, shape, floats) ⇒ Object
Class Method Details
.checked_offsets(name, offsets, body_size) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/static_embeddings/safetensors.rb', line 63 def checked_offsets(name, offsets, body_size) unless offsets.is_a?(Array) && offsets.length == 2 raise ConversionError, "tensor #{name}: malformed data_offsets" end begin_off, end_off = offsets unless begin_off.is_a?(Integer) && end_off.is_a?(Integer) && begin_off >= 0 && end_off >= begin_off && end_off <= body_size raise ConversionError, "tensor #{name}: data_offsets out of bounds" end [begin_off, end_off] end |
.checked_tensor_bytes(name, dtype, shape) ⇒ Object
77 78 79 80 81 82 83 84 85 |
# File 'lib/static_embeddings/safetensors.rb', line 77 def checked_tensor_bytes(name, dtype, shape) element_size = SUPPORTED_DTYPES[dtype] raise ConversionError, "tensor #{name}: dtype #{dtype} is not supported (F32 only)" unless element_size unless shape.is_a?(Array) && shape.all? { |dim| dim.is_a?(Integer) && dim >= 0 } raise ConversionError, "tensor #{name}: malformed shape #{shape.inspect}" end shape.reduce(1, :*) * element_size end |
.parse_header(data) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/static_embeddings/safetensors.rb', line 31 def parse_header(data) raise ConversionError, "safetensors file is shorter than its length prefix" if data.bytesize < 8 header_len = data.byteslice(0, 8).unpack1("Q<") unless header_len.positive? && header_len <= MAX_HEADER_BYTES raise ConversionError, "implausible safetensors header length #{header_len}" end body_offset = 8 + header_len raise ConversionError, "safetensors header runs past end of file" if body_offset > data.bytesize raw_header = data.byteslice(8, header_len) raise ConversionError, "safetensors header is not a JSON object" unless raw_header.lstrip.start_with?("{") [JSON.parse(raw_header), body_offset] end |
.read(path) ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/static_embeddings/safetensors.rb', line 10 def read(path) data = File.binread(path) header, body_offset = parse_header(data) body_size = data.bytesize - body_offset tensors = header.each_with_object({}) do |(name, spec), acc| next if name == "__metadata__" acc[name] = tensor_from(data, body_offset, body_size, name, spec) end { metadata: header["__metadata__"] || {}, tensors: tensors } end |
.tensor_from(data, body_offset, body_size, name, spec) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/static_embeddings/safetensors.rb', line 48 def tensor_from(data, body_offset, body_size, name, spec) dtype = spec["dtype"] shape = spec["shape"] begin_off, end_off = checked_offsets(name, spec["data_offsets"], body_size) expected = checked_tensor_bytes(name, dtype, shape) actual = end_off - begin_off if actual != expected raise ConversionError, "tensor #{name}: shape #{shape.inspect} implies #{expected} bytes, offsets span #{actual}" end { dtype: dtype, shape: shape, bytes: data.byteslice(body_offset + begin_off, actual) } end |
.write(path, name, shape, floats) ⇒ Object
24 25 26 27 28 29 |
# File 'lib/static_embeddings/safetensors.rb', line 24 def write(path, name, shape, floats) body = floats.pack("e*") header = JSON.generate(name => { "dtype" => "F32", "shape" => shape, "data_offsets" => [0, body.bytesize] }) padded = header << (" " * ((8 - (header.bytesize % 8)) % 8)) File.binwrite(path, [padded.bytesize].pack("Q<") << padded << body) end |