Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 1 addition & 40 deletions BackendKind.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1 @@
export type BackendKind = "webnn" | "webgpu" | "webgl" | "wasm";

function hasWebGpuSupport(): boolean {
return (
typeof navigator !== "undefined" &&
typeof (navigator as Navigator & { gpu?: unknown }).gpu !== "undefined"
);
}

function hasWebGl2Support(): boolean {
if (typeof document === "undefined") {
return false;
}

const canvas = document.createElement("canvas");
return canvas.getContext("webgl2") !== null;
}

function hasWebNnSupport(): boolean {
return (
typeof navigator !== "undefined" &&
typeof (navigator as Navigator & { ml?: unknown }).ml !== "undefined"
);
}

export function detectBackend(): BackendKind {
if (hasWebGpuSupport()) {
return "webgpu";
}

if (hasWebGl2Support()) {
return "webgl";
}

if (hasWebNnSupport()) {
return "webnn";
}

return "wasm";
}
export * from "./lib/BackendKind";
29 changes: 1 addition & 28 deletions CreateVectorBackend.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
import { detectBackend } from "./BackendKind";
import type { VectorBackend } from "./VectorBackend";
import { WasmVectorBackend } from "./WasmVectorBackend";
import { WebGlVectorBackend } from "./WebGLVectorBackend";
import { WebGpuVectorBackend } from "./WebGPUVectorBackend";
import { WebNnVectorBackend } from "./WebNNVectorBackend";

export async function createVectorBackend(
wasmBytes: ArrayBuffer
): Promise<VectorBackend> {
const kind = detectBackend();
if (kind === "webgpu") {
return WebGpuVectorBackend.create().catch(() =>
WasmVectorBackend.create(wasmBytes)
);
}
if (kind === "webgl") {
return Promise.resolve(WebGlVectorBackend.create()).catch(() =>
WasmVectorBackend.create(wasmBytes)
);
}
if (kind === "webnn") {
return WebNnVectorBackend.create(wasmBytes).catch(() =>
WasmVectorBackend.create(wasmBytes)
);
}
return WasmVectorBackend.create(wasmBytes);
}
export * from "./lib/CreateVectorBackend";
2 changes: 1 addition & 1 deletion PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ This document tracks the implementation status of each major module in CORTEX. I

| Module | Status | Files | Notes |
|--------|--------|-------|-------|
| Browser Harness | ✅ Complete | `runtime/harness/index.html`, `scripts/runtime-harness-server.mjs` | Localhost-served HTML harness for browser testing |
| Browser Harness | ✅ Complete | `ui/harness/index.html`, `scripts/runtime-harness-server.mjs` | Localhost-served HTML harness for browser testing |
| Electron Wrapper | ✅ Complete | `scripts/electron-harness-main.mjs` | Thin Electron launcher for GPU-realism testing |
| Playwright Tests | ✅ Complete | `tests/runtime/browser-harness.spec.mjs`, `tests/runtime/electron-harness.spec.mjs` | Browser lane passes; Electron context-sensitive |
| Docker Debug Lane | ✅ Complete | `docker/electron-debug/*`, `docker-compose.electron-debug.yml` | Sandbox-isolated Electron debugging via VS Code attach |
Expand Down
148 changes: 1 addition & 147 deletions Policy.ts
Original file line number Diff line number Diff line change
@@ -1,147 +1 @@
import type { ModelProfile } from "./core/ModelProfile";
import {
ModelProfileResolver,
type ModelProfileResolverOptions,
type ResolveModelProfileInput,
} from "./core/ModelProfileResolver";

export type QueryScope = "broad" | "normal" | "narrow" | "default";

export interface ProjectionHead {
dimIn: number;
dimOut: number;
bits?: number;
// Byte offset for the projection head in a flattened projection buffer.
offset: number;
}

export interface RoutingPolicy {
broad: ProjectionHead;
normal: ProjectionHead;
narrow: ProjectionHead;
}

export interface ResolvedRoutingPolicy {
modelProfile: ModelProfile;
routingPolicy: RoutingPolicy;
}

export interface ResolveRoutingPolicyOptions {
resolver?: ModelProfileResolver;
resolverOptions?: ModelProfileResolverOptions;
routingPolicyOverrides?: Partial<RoutingPolicyDerivation>;
}

export interface RoutingPolicyDerivation {
broadDimRatio: number;
normalDimRatio: number;
narrowDimRatio: number;
broadHashBits: number;
dimAlignment: number;
minProjectionDim: number;
}

export const DEFAULT_ROUTING_POLICY_DERIVATION: RoutingPolicyDerivation =
Object.freeze({
broadDimRatio: 1 / 8,
normalDimRatio: 1 / 4,
narrowDimRatio: 1 / 2,
broadHashBits: 128,
dimAlignment: 8,
minProjectionDim: 8,
});

function assertPositiveInteger(name: string, value: number): void {
if (!Number.isInteger(value) || value <= 0) {
throw new Error(`${name} must be a positive integer`);
}
}

function assertPositiveFinite(name: string, value: number): void {
if (!Number.isFinite(value) || value <= 0) {
throw new Error(`${name} must be positive and finite`);
}
}

function alignDown(value: number, alignment: number): number {
return Math.floor(value / alignment) * alignment;
}

function deriveProjectionDim(
dimIn: number,
ratio: number,
derivation: RoutingPolicyDerivation,
): number {
const raw = Math.floor(dimIn * ratio);
const aligned = alignDown(raw, derivation.dimAlignment);
const bounded = Math.max(derivation.minProjectionDim, aligned);
return Math.min(dimIn, bounded);
}

function validateDerivation(derivation: RoutingPolicyDerivation): void {
assertPositiveFinite("broadDimRatio", derivation.broadDimRatio);
assertPositiveFinite("normalDimRatio", derivation.normalDimRatio);
assertPositiveFinite("narrowDimRatio", derivation.narrowDimRatio);
assertPositiveInteger("broadHashBits", derivation.broadHashBits);
assertPositiveInteger("dimAlignment", derivation.dimAlignment);
assertPositiveInteger("minProjectionDim", derivation.minProjectionDim);
}

export function createRoutingPolicy(
modelProfile: Pick<ModelProfile, "embeddingDimension">,
overrides: Partial<RoutingPolicyDerivation> = {},
): RoutingPolicy {
assertPositiveInteger("embeddingDimension", modelProfile.embeddingDimension);

const derivation: RoutingPolicyDerivation = {
...DEFAULT_ROUTING_POLICY_DERIVATION,
...overrides,
};

validateDerivation(derivation);

const dimIn = modelProfile.embeddingDimension;
const broadDim = deriveProjectionDim(dimIn, derivation.broadDimRatio, derivation);
const normalDim = deriveProjectionDim(dimIn, derivation.normalDimRatio, derivation);
const narrowDim = deriveProjectionDim(dimIn, derivation.narrowDimRatio, derivation);

const broadOffset = 0;
const normalOffset = broadOffset + broadDim * dimIn;
const narrowOffset = normalOffset + normalDim * dimIn;

return {
broad: {
dimIn,
dimOut: broadDim,
bits: derivation.broadHashBits,
offset: broadOffset,
},
normal: {
dimIn,
dimOut: normalDim,
offset: normalOffset,
},
narrow: {
dimIn,
dimOut: narrowDim,
offset: narrowOffset,
},
};
}

export function resolveRoutingPolicyForModel(
input: ResolveModelProfileInput,
options: ResolveRoutingPolicyOptions = {},
): ResolvedRoutingPolicy {
const resolver =
options.resolver ?? new ModelProfileResolver(options.resolverOptions);
const modelProfile = resolver.resolve(input);

return {
modelProfile,
routingPolicy: createRoutingPolicy(
modelProfile,
options.routingPolicyOverrides,
),
};
}
export * from "./lib/Policy";
29 changes: 1 addition & 28 deletions TopK.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1 @@
import type { DistanceResult, ScoreResult } from "./VectorBackend";

export function topKByScore(scores: Float32Array, k: number): ScoreResult[] {
const limit = Math.max(0, Math.min(k, scores.length));
const indices = Array.from({ length: scores.length }, (_, i) => i);

indices.sort((a, b) => scores[b] - scores[a]);

return indices.slice(0, limit).map((index) => ({
index,
score: scores[index]
}));
}

export function topKByDistance(
distances: Uint32Array | Int32Array | Float32Array,
k: number
): DistanceResult[] {
const limit = Math.max(0, Math.min(k, distances.length));
const indices = Array.from({ length: distances.length }, (_, i) => i);

indices.sort((a, b) => Number(distances[a]) - Number(distances[b]));

return indices.slice(0, limit).map((index) => ({
index,
distance: Number(distances[index])
}));
}
export * from "./lib/TopK";
50 changes: 1 addition & 49 deletions VectorBackend.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1 @@
import type { BackendKind } from "./BackendKind";

export interface ScoreResult {
index: number;
score: number;
}

export interface DistanceResult {
index: number;
distance: number;
}

export interface VectorBackend {
kind: BackendKind;

// Exact or high-precision dot-product scoring over row-major matrices.
dotMany(
query: Float32Array,
matrix: Float32Array,
dim: number,
count: number
): Promise<Float32Array>;

// Projection helper used to reduce dimensionality for routing tiers.
project(
vector: Float32Array,
projectionMatrix: Float32Array,
dimIn: number,
dimOut: number
): Promise<Float32Array>;

topKFromScores(scores: Float32Array, k: number): Promise<ScoreResult[]>;

// Random-hyperplane hash from projected vectors into packed binary codes.
hashToBinary(
vector: Float32Array,
projectionMatrix: Float32Array,
dimIn: number,
bits: number
): Promise<Uint32Array>;

hammingTopK(
queryCode: Uint32Array,
codes: Uint32Array,
wordsPerCode: number,
count: number,
k: number
): Promise<DistanceResult[]>;
}
export * from "./lib/VectorBackend";
Loading
Loading