Skip to content

Commit 025460c

Browse files
authored
🤖 perf: speed up CI with approx tokenizer and parallel static-check (#962)
Speeds up CI by: 1. **Approximate tokenizer for tests** - Defaults Jest to `length/4` token counting instead of loading the 10s WASM tokenizer 2. **Parallel static-check** - Removes `-j3` limit, allowing all 6 subtasks to run in parallel 3. **Shallow checkout for tests** - Test jobs don't need full git history ## Changes ### Tokenizer (`src/node/utils/main/tokenizer.ts`) - Add `shouldUseApproxTokenizer()` that checks `MUX_APPROX_TOKENIZER=1` - Short-circuit all tokenizer functions when in approx mode - Override with `MUX_FORCE_REAL_TOKENIZER=1` for tests needing real tokenization ### Test setup (`tests/setup.ts`) - Default `MUX_APPROX_TOKENIZER=1` in Jest ### CI (`.github/workflows/pr.yml`) - `make -j static-check` (was `-j3`) - Remove `fetch-depth: 0` from test-unit, test-integration, test-storybook, test-e2e ### Test fix (`src/cli/run.test.ts`) - Update version test to accept both `vX.Y.Z` and hash-only formats (shallow clones don't have tags) _Generated with `mux`_
1 parent a64c1c3 commit 025460c

File tree

4 files changed

+55
-11
lines changed

4 files changed

+55
-11
lines changed

‎.github/workflows/pr.yml‎

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
experimental-features = nix-command flakes
7878
- run: curl -LsSf https://astral.sh/uv/install.sh | sh
7979
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH
80-
- run: make -j3 static-check
80+
- run: make -j static-check
8181

8282
test-unit:
8383
name: Test / Unit
@@ -86,8 +86,6 @@ jobs:
8686
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
8787
steps:
8888
- uses: actions/checkout@v4
89-
with:
90-
fetch-depth: 0
9189
- uses: ./.github/actions/setup-mux
9290
- run: make build-main
9391
- run: bun test --coverage --coverage-reporter=lcov ${{ github.event.inputs.test_filter || 'src' }}
@@ -106,8 +104,6 @@ jobs:
106104
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
107105
steps:
108106
- uses: actions/checkout@v4
109-
with:
110-
fetch-depth: 0
111107
- uses: ./.github/actions/setup-mux
112108
- run: make build-main
113109
- name: Run integration tests
@@ -146,8 +142,6 @@ jobs:
146142
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
147143
steps:
148144
- uses: actions/checkout@v4
149-
with:
150-
fetch-depth: 0
151145
- uses: ./.github/actions/setup-mux
152146
- uses: ./.github/actions/setup-playwright
153147
- run: make storybook-build
@@ -171,8 +165,6 @@ jobs:
171165
runs-on: ${{ matrix.runner }}
172166
steps:
173167
- uses: actions/checkout@v4
174-
with:
175-
fetch-depth: 0
176168
- uses: ./.github/actions/setup-mux
177169
- name: Install xvfb
178170
if: matrix.os == 'linux'

‎src/cli/run.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ describe("mux CLI", () => {
102102
test("--version shows version info", async () => {
103103
const result = await runCli(["--version"]);
104104
expect(result.exitCode).toBe(0);
105-
// Version format: vX.Y.Z-N-gHASH (HASH)
106-
expect(result.stdout).toMatch(/v\d+\.\d+\.\d+/);
105+
// Version format: vX.Y.Z-N-gHASH (HASH) or just HASH (HASH) in shallow clones
106+
expect(result.stdout).toMatch(/v\d+\.\d+\.\d+|^[0-9a-f]{7,}/);
107107
});
108108

109109
test("unknown command shows error", async () => {

‎src/node/utils/main/tokenizer.ts‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,31 @@ export interface Tokenizer {
1818
countTokens: (text: string) => Promise<number>;
1919
}
2020

21+
const APPROX_ENCODING = "approx-4";
22+
23+
function shouldUseApproxTokenizer(): boolean {
24+
// MUX_FORCE_REAL_TOKENIZER=1 overrides approx mode (for tests that need real tokenization)
25+
// MUX_APPROX_TOKENIZER=1 enables fast approximate mode (default in Jest)
26+
if (process.env.MUX_FORCE_REAL_TOKENIZER === "1") {
27+
return false;
28+
}
29+
return process.env.MUX_APPROX_TOKENIZER === "1";
30+
}
31+
32+
function approximateCount(text: string): number {
33+
if (typeof text !== "string" || text.length === 0) {
34+
return 0;
35+
}
36+
return Math.ceil(text.length / 4);
37+
}
38+
39+
function getApproxTokenizer(): Tokenizer {
40+
return {
41+
encoding: APPROX_ENCODING,
42+
countTokens: (input: string) => Promise.resolve(approximateCount(input)),
43+
};
44+
}
45+
2146
const encodingPromises = new Map<ModelName, Promise<string>>();
2247
const inFlightCounts = new Map<string, Promise<number>>();
2348
const tokenCountCache = new LRUCache<string, number>({
@@ -138,6 +163,14 @@ async function countTokensInternal(modelName: ModelName, text: string): Promise<
138163
export function loadTokenizerModules(
139164
modelsToWarm: string[] = Array.from(DEFAULT_WARM_MODELS)
140165
): Promise<Array<PromiseSettledResult<string>>> {
166+
if (shouldUseApproxTokenizer()) {
167+
const fulfilled: Array<PromiseFulfilledResult<string>> = modelsToWarm.map(() => ({
168+
status: "fulfilled",
169+
value: APPROX_ENCODING,
170+
}));
171+
return Promise.resolve(fulfilled);
172+
}
173+
141174
return Promise.allSettled(
142175
modelsToWarm.map((modelString) => {
143176
const modelName = normalizeModelKey(modelString);
@@ -151,6 +184,10 @@ export function loadTokenizerModules(
151184
}
152185

153186
export async function getTokenizerForModel(modelString: string): Promise<Tokenizer> {
187+
if (shouldUseApproxTokenizer()) {
188+
return getApproxTokenizer();
189+
}
190+
154191
const modelName = resolveModelName(modelString);
155192
const encodingName = await resolveEncoding(modelName);
156193

@@ -161,12 +198,21 @@ export async function getTokenizerForModel(modelString: string): Promise<Tokeniz
161198
}
162199

163200
export function countTokens(modelString: string, text: string): Promise<number> {
201+
if (shouldUseApproxTokenizer()) {
202+
return Promise.resolve(approximateCount(text));
203+
}
204+
164205
const modelName = resolveModelName(modelString);
165206
return countTokensInternal(modelName, text);
166207
}
167208

168209
export function countTokensBatch(modelString: string, texts: string[]): Promise<number[]> {
169210
assert(Array.isArray(texts), "Batch token counting expects an array of strings");
211+
212+
if (shouldUseApproxTokenizer()) {
213+
return Promise.resolve(texts.map((text) => approximateCount(text)));
214+
}
215+
170216
const modelName = resolveModelName(modelString);
171217
return Promise.all(texts.map((text) => countTokensInternal(modelName, text)));
172218
}

‎tests/setup.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import assert from "assert";
77
import "disposablestack/auto";
88

99
assert.equal(typeof Symbol.dispose, "symbol");
10+
// Use fast approximate token counting in Jest to avoid 10s WASM cold starts
11+
// Individual tests can override with MUX_FORCE_REAL_TOKENIZER=1
12+
if (process.env.MUX_FORCE_REAL_TOKENIZER !== "1") {
13+
process.env.MUX_APPROX_TOKENIZER ??= "1";
14+
}
15+
1016
assert.equal(typeof Symbol.asyncDispose, "symbol");
1117

1218
// Polyfill File for undici in jest environment

0 commit comments

Comments
 (0)