/**
* multimodal_vocab.ts — one flat token space shared by text and video.
*
* Evermind's generator ({@link ../lm/evermind_lm.EvermindLM}) is a pure
* next-token predictor: it neither knows nor cares whether a token id is a BPE
* text piece or a quantised video-patch code. So "make Evermind generate video"
* is a *vocabulary* problem, not a model problem — lay text tokens, a handful of
* modality control tokens, and the video codebook codes out on a single integer
* axis, and the same model handles `(caption … … frames … )`.
*
* Layout (low → high):
*
* [ 0 .. textVocabSize ) text (BPE) — passthrough
* textVocabSize + 0 (BOS_VIDEO)
* textVocabSize + 1 (EOS_VIDEO)
* textVocabSize + 2 (start of an intra frame)
* textVocabSize + 3 (start of an inter frame)
* codeBase + bank·levels·K + level·K + code residual-VQ code tokens
*
* where `bank ∈ {INTRA, INTER}`, `level ∈ [0, levels)`, `code ∈ [0, K)` and
* `K = codebookSize`. A frame is therefore a marker followed by
* `patchesPerFrame · levels` code tokens — a deterministic, self-delimiting
* structure the codec can parse straight out of the generator's output.
*/
export const VIDEO_BANK_INTRA = 0;
export const VIDEO_BANK_INTER = 1;
export interface MultimodalVocabConfig {
/** Size of the text (BPE) region; 0 for a pure-video model. */
textVocabSize: number;
/** Residual-VQ depth (codes emitted per patch). */
levels: number;
/** Entries per codebook per level per bank. */
codebookSize: number;
}
/** What a token id decodes to when parsing a mixed stream. */
export type TokenKind =
| { kind: "text"; id: number }
| { kind: "control"; control: "bosVideo" | "eosVideo" | "frameKey" | "frameDelta" }
| { kind: "code"; bank: number; level: number; code: number };
/**
* Bijective map between (text ids, control tokens, video codes) and a single
* flat token id space. Owns the offset arithmetic so nothing else has to.
*/
export class MultimodalVocab {
readonly textVocabSize: number;
readonly levels: number;
readonly codebookSize: number;
readonly bosVideo: number;
readonly eosVideo: number;
readonly frameKey: number;
readonly frameDelta: number;
readonly codeBase: number;
/** Total vocabulary size — pass this as EvermindLM's `vocabSize`. */
readonly size: number;
private readonly perBank: number;
constructor(cfg: MultimodalVocabConfig) {
if (cfg.textVocabSize < 0) throw new Error("MultimodalVocab: textVocabSize must be ≥ 0");
if (cfg.levels <= 0 || cfg.codebookSize <= 0) throw new Error("MultimodalVocab: levels and codebookSize must be > 0");
this.textVocabSize = cfg.textVocabSize;
this.levels = cfg.levels;
this.codebookSize = cfg.codebookSize;
this.bosVideo = cfg.textVocabSize;
this.eosVideo = cfg.textVocabSize + 1;
this.frameKey = cfg.textVocabSize + 2;
this.frameDelta = cfg.textVocabSize + 3;
this.codeBase = cfg.textVocabSize + 4;
this.perBank = cfg.levels * cfg.codebookSize;
this.size = this.codeBase + 2 * this.perBank;
}
/** Global token id for a residual-VQ code. */
codeToken(bank: number, level: number, code: number): number {
return this.codeBase + bank * this.perBank + level * this.codebookSize + code;
}
isText(tok: number): boolean {
return tok >= 0 && tok < this.textVocabSize;
}
isCode(tok: number): boolean {
return tok >= this.codeBase && tok < this.size;
}
isVideoMarker(tok: number): boolean {
return tok === this.frameKey || tok === this.frameDelta;
}
/** Decompose a code token into (bank, level, code). Level/code are meaningful even if the bank differs. */
decodeCode(tok: number): { bank: number; level: number; code: number } {
const idx = tok - this.codeBase;
const bank = Math.floor(idx / this.perBank);
const rem = idx - bank * this.perBank;
const level = Math.floor(rem / this.codebookSize);
const code = rem - level * this.codebookSize;
return { bank, level, code };
}
/** Classify any token id in the flat space. */
classify(tok: number): TokenKind {
if (this.isText(tok)) return { kind: "text", id: tok };
if (tok === this.bosVideo) return { kind: "control", control: "bosVideo" };
if (tok === this.eosVideo) return { kind: "control", control: "eosVideo" };
if (tok === this.frameKey) return { kind: "control", control: "frameKey" };
if (tok === this.frameDelta) return { kind: "control", control: "frameDelta" };
return { kind: "code", ...this.decodeCode(tok) };
}
}