Skip to content

embed: record embedding models and compare their vectors - #4

Merged
midagedev merged 6 commits into
mainfrom
embed-tapes
Oct 15, 2026
Merged

midagedev merged 6 commits into
mainfrom
embed-tapes

Conversation

@midagedev

@midagedev midagedev commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Adds toktape embed, which records embedding models. The goal is to record the EmbeddingGemma 2 work tracked in bloomery #190 and later check bloomery's vectors against llama-server's.

Format. This is a new tape mode, embedding. It's built like the decision tape: Requests stays empty, the run lives in Tape.Embeddings, and its summary lives in Summary.Embedding.

  • Each record holds:
    • the id and input
    • send and answer times
    • prompt_tokens, when the server reports them
    • dim, norm and the first 8 values, all taken from the full vector
    • the vector itself as base64 little-endian f32
  • --vector-cap N can trim the stored vector. Dim, norm and head are still kept whole.
  • The summary records the API (openai → POST /v1/embeddings, llama → POST /embedding), endpoint, model, dimensions, encoding_format, input set, dim, and latency p50/max.
  • Responses are read as float arrays, base64, or llama's one-row matrix. If llama returns several rows (unpooled output), the run fails and the error names --pooling.

Views.

  • The default toktape play screen for an embedding tape is a search scene. It's used whenever the inputs carry a role. Every ranking on it is computed from the recorded vectors when the screen is drawn, as a pure function of the tape and the replay time:
    • Index: the 20 docs arrive one by one on the left, each with its topic colour, text and real latency.
    • Search: on the right, each query is typed out, embedded live, and answered with a top-5 list. Score bars appear one row at a time, the expected doc is marked ✓, and the hits are marked in the index column with their rank.
    • Prompts: shows the expected doc's rank and its lead over the next doc, both with EmbeddingGemma 2's task prompts and with the text sent bare.
    • MRL: shows the expected doc's rank at 768, 256 and 128 dims (truncate, then cosine renormalises), and whether the top hit stays the same.
    • Earlier: one line per earlier query.
    • Clusters: after the last query, the doc × doc cosine matrix ordered by topic, with same-topic cells in bold, and recall@1 with and without the prompts.
  • Layout: the record screen's machine pane (rightPane: SPEED, PLACEMENT, MEMORY, RESOURCES) sits on the right of the search scene. On an embedding tape, SPEED shows requests, latency p50 and prompt tokens instead of tok/s. toktape embed now samples the host and the server's process every 500ms with the record path's sampler, so RSS, CPU and load are on the tape.
  • Motion: everything that moves is a function of replay time. Score bars and the figures beside them ease out to their value (350ms). Ranked rows slide in staggered and come up from dim. The query's vector strip, and in --verify each input's strip, fills left to right. The item in flight pulses instead of showing a spinner. The prompt and MRL figures tween in one after another and the margins count up. The typing cursor blinks, and typing speed wavers per character (a fixed per-character hash, so every replay is the same). The cluster matrix fades in one topic block at a time.
  • Frame rate: toktape play redraws an embedding tape at about 30fps while the scene runs and drops to 4fps once it has settled.
  • toktape play --verify keeps the per-input verify screen: vector strips, norm and the coloured cosine matrix. --against b.tape adds a cosine bar per input with the band at 0.999; records are matched by id and prompt.
  • toktape card prints the text view, and toktape compare a b gives the per-input cosine and max |diff| as text.

Task prompts. The strings are taken verbatim from the prompts in google/embeddinggemma-2's config_sentence_transformers.json:

  • query: "task: search result | query: "
  • document: "title: none | text: "

Each labelled row is sent once with its prompt and once bare. --no-prompts sends every input bare only.

Pacing. --gap (100ms after each doc), --type-rate (25ms per query character, which the replay shows as typing) and --hold (1s after each query, plus a short closing hold) control how long the replay takes; the whole demo replays in about 16s. --no-pace turns them all off. The idle time is stored in the timestamps (shown_at, outro_at, scene_end) and is never part of a latency.

Inputs. The built-in set is a search corpus:

  • 20 short docs, mixed Korean and English, in 5 topics: cooking, travel, code, health and finance.
  • 6 queries, each with an expect doc. They include Korean queries that should find English docs and English queries that should find Korean docs.

--inputs takes your own JSONL of {"id","input"} rows, optionally with "role" (doc or query), "topic" and "expect".

Demo. Real recordings of embeddinggemma-2-Q8_0.gguf served by mainline llama-server with --embeddings, 768 dims:

  • assets/embedding-demo.tape: llama-server 843e7dc built with CUDA, the model on one RTX 3090 (CUDA_VISIBLE_DEVICES, -ngl 99), /v1/embeddings with pacing. Request p50 6.6 ms (max 60.9 ms, the first request). The host and GPU samples are on the tape, so the right pane shows GPU0/GPU1. It replays in about 16s; the clusters view starts at about 12s.
  • internal/tape/testdata/embedding-llama.tape: a CPU recording (llama-server a0e6dc7), /embedding with --no-pace.
  • internal/tape/testdata/embedding-llama.tape uses /embedding with --no-pace. It's used by the --against test.

On this set recall@1 is 6/6 both with the prompts and bare; the prompts mostly widen the margin over the next doc, and the top hit holds at 256 and 128 dims.

Publishing. toktape publish and toktape share accept embedding tapes:

  • The publish refusals are gone (publish.ErrEmbedding is removed).
  • PNG card (toktape card -o png, and the card the upload carries): the decision card's frame with the request p50 as the hero, recall@1 beside it (with prompts, and bare), and dim/docs/queries/slowest in the mid band. The summary gains hits, hits_bare and graded, computed at record time from the vectors, and the at-end memory and GPU readings.
  • Index row: mode = "embedding", no stream count, and the request p50 in decision_p50_ms (no new column, no migration). The run page labels it by mode ("request p50", "embedding model").
  • --no-text removes the inputs (the scene then shows no text) and the dry run counts the input characters that would travel.
  • Web player: render.RunEnd ends an embedding tape at the scene's end, so the wasm player replays the search scene with the right pane, GPU rows included. web/player/smoke.mjs loads the demo tape and checks for the scene. brotli is 1,360,854 bytes, under the 1,700,000 budget.

Not in this PR. toktape render (GIF/MP4 clips) still refuses embedding tapes, and the README isn't updated, since the translations need to be written by hand.

./scripts/check.sh passes. There are new tests in internal/tape, internal/publish, internal/card/png, internal/recorder, internal/tui and cmd/toktape. The internal/tui tests walk the search scene through indexing, typing, the answer (✓, prompts, MRL) and the clusters at 120×40 and 100×30. They also check the right pane, motion (typing never goes backwards and is complete when the query is sent, the frame changes while it animates and stays still once settled), that the demo replay is 15–25s, that the plain and colour renderings match, the verify and --against screens, and the strip and downsample code.

Note: I changed the internal/tape schema (only additive fields), so it needs a schema review.

— written by the bloomery AI assistant

…ped queries, top-k, task prompts, MRL, topic clusters; --verify keeps the per-input screen
…ulse, typed queries blink, clusters fade in by topic; machine pane on the right; embed records host samples
…cing

publish/share accept embedding tapes; the PNG card has an embedding
layout (request p50, recall@1); the index row carries mode and the
request p50; the web player replays the search scene with the resource
panel. The demo tape is re-recorded on an RTX 3090 with GPU samples.

Written by the bloomery AI assistant.
@midagedev
midagedev merged commit 67be1a1 into main Oct 15, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant