Skip to content

Embedding Format Converter

Convert embeddings between JSON, CSV, the raw base64 float buffer and a real .npy file, in either direction, with the precision loss measured.

Largest value changed by
1.35e-8

Decoded the output again and compared all 24 values: 24 differ. This is the precision you are giving up, measured rather than assumed.

Vectors × dimensions
3 × 8
Mean absolute change
4.09e-9
Raw buffer size
96 bytes
JSON at 6 places, for comparison
213 bytes
Output characters
128
Notes on this conversion:
  • Parquet is not offered as an output. It is a columnar container with a Thrift-encoded footer, and writing one honestly means shipping a large library to a page whose whole point is that it is small — while anyone who wants parquet already has pyarrow. Take the JSON output and run: pa.table({"embedding": rows}) then pq.write_table(t, "vectors.parquet").
What this assumes: the values are a rectangular float matrix — every vector the same length — and anything else is refused with the row that broke it rather than padded. Raw base64 is the float buffer and nothing else: no shape, no dtype, no header, which is why the dimensions and width are fields you set. The .npy output is a real version 1.0 file, little-endian, C-order, header padded to a 64-byte boundary; save the base64 with base64 -d > vectors.npy and numpy will load it. Everything on this page runs in your browser. Nothing you paste is uploaded, logged or sent anywhere.

Embeddings move between systems more often than anyone plans for: out of an API as JSON, into a notebook as a numpy array, into a database as a base64 column, out of a backup as raw bytes with the dimensions written on a sticky note. Each hop is a chance to get the width or the byte order wrong, and the failure mode is not an error — it is a matrix of plausible-looking numbers whose nearest neighbours are nonsense.

Why float32 is the interesting number

Most embedding APIs return float64-looking JSON and most vector stores keep float32. Halving the width halves the storage and the memory bandwidth of every search, and the cost is roughly seven significant decimal digits instead of sixteen. The headline above measures that cost on your vectors rather than describing it: convert to float32 and it shows the largest value that moved. On normalised embeddings the answer is typically around a ten-millionth, which is far below the precision of the model that produced them — which is why almost everyone does it.

The failure this catches

Raw base64 is the format that goes wrong, because it carries nothing but bytes. A buffer written as float64 and read as float32 decodes to twice as many values, all garbage, and the only signal is that the count is wrong — which is why this tool refuses a byte length that is not a whole number of vectors and tells you how many bytes were left over. That single check catches the width mistake, the dimension mistake and the truncated-download mistake in one.

And .npy, which carries its own answers

The numpy format exists because of exactly that problem: a 10-byte magic, a short Python dict giving dtype, shape and memory order, then the buffer. It is small enough to write by hand — this page does — and it means the file describes itself. If you are moving vectors between machines and you get to choose, choose the format that knows what it is.

Embedding Format Converter · Multigrid