Files
wehub-resource-sync 311a3666c4
Build documentation / build (push) Failing after 0s
Unit tests / build (18) (push) Has been cancelled
Unit tests / build (20) (push) Has been cancelled
Unit tests / build (22) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:44:39 +08:00

592 lines
17 KiB
JavaScript

import {
// Models
AutoModelForSeq2SeqLM,
AutoModelForCausalLM,
LlamaForCausalLM,
// Tokenizers
AutoTokenizer,
LlamaTokenizer,
// Other
TextStreamer,
DynamicCache,
random,
full,
} from "../../src/transformers.js";
import { init, MAX_TEST_EXECUTION_TIME, MAX_MODEL_LOAD_TIME, MAX_MODEL_DISPOSE_TIME, DEFAULT_MODEL_OPTIONS } from "../init.js";
// Initialise the testing environment
init();
// Helper function to generate text
const generate = async (model, tokenizer, text, options) => {
const inputs = tokenizer(text);
return await model.generate({
...inputs,
...options,
});
};
describe("Generation parameters", () => {
// List all models which will be tested
const models = [
"hf-internal-testing/tiny-random-T5ForConditionalGeneration", // encoder-decoder
"hf-internal-testing/tiny-random-LlamaForCausalLM", // decoder-only
];
const DUMMY_TEXT = "hello";
describe(`encoder-decoder (${models[0]})`, () => {
const model_id = models[0];
let model;
let tokenizer;
beforeAll(async () => {
model = await AutoModelForSeq2SeqLM.from_pretrained(model_id, DEFAULT_MODEL_OPTIONS);
tokenizer = await AutoTokenizer.from_pretrained(model_id);
}, MAX_MODEL_LOAD_TIME);
// NOTE: Since `max_length` defaults to 20, this case also tests that.
it(
"default",
async () => {
const outputs = await generate(model, tokenizer, DUMMY_TEXT, {});
expect(outputs.dims.at(-1)).toEqual(20);
},
MAX_TEST_EXECUTION_TIME,
);
it(
"max_new_tokens",
async () => {
const MAX_NEW_TOKENS = 5;
const outputs = await generate(model, tokenizer, DUMMY_TEXT, {
max_new_tokens: MAX_NEW_TOKENS,
});
expect(outputs.dims.at(-1)).toEqual(MAX_NEW_TOKENS + 1); // + 1 due to forced BOS token
},
MAX_TEST_EXECUTION_TIME,
);
it(
"min_length",
async () => {
const MIN_LENGTH = 3;
const MAX_LENGTH = 5;
const outputs = await generate(model, tokenizer, DUMMY_TEXT, {
eos_token_id: 0,
min_length: MIN_LENGTH,
max_length: MAX_LENGTH,
});
expect(outputs.tolist()).toEqual([[0n, 11924n, 11924n, 11924n, 11924n]]);
expect(outputs.dims.at(-1)).toBeGreaterThanOrEqual(MIN_LENGTH);
},
MAX_TEST_EXECUTION_TIME,
);
it(
"min_new_tokens",
async () => {
const MIN_NEW_TOKENS = 2;
const MAX_LENGTH = 5;
const outputs = await generate(model, tokenizer, DUMMY_TEXT, {
eos_token_id: 0,
min_new_tokens: MIN_NEW_TOKENS,
max_length: MAX_LENGTH,
});
expect(outputs.tolist()).toEqual([[0n, 11924n, 11924n, 11924n, 11924n]]);
expect(outputs.dims.at(-1)).toBeGreaterThanOrEqual(MIN_NEW_TOKENS);
},
MAX_TEST_EXECUTION_TIME,
);
afterAll(async () => {
await model?.dispose();
}, MAX_MODEL_DISPOSE_TIME);
});
describe(`decoder-only (${models[1]})`, () => {
const model_id = models[1];
let model;
let tokenizer;
beforeAll(async () => {
model = await AutoModelForCausalLM.from_pretrained(model_id, DEFAULT_MODEL_OPTIONS);
tokenizer = await AutoTokenizer.from_pretrained(model_id);
}, MAX_MODEL_LOAD_TIME);
// NOTE: Since `max_length` defaults to 20, this case also tests that.
it(
"default",
async () => {
const outputs = await generate(model, tokenizer, DUMMY_TEXT, {});
expect(outputs.dims.at(-1)).toEqual(20);
},
MAX_TEST_EXECUTION_TIME,
);
it(
"max_new_tokens",
async () => {
const MAX_NEW_TOKENS = 5;
const PROMPT_LENGTH = 2; // BOS + DUMMY_TEXT
const outputs = await generate(model, tokenizer, DUMMY_TEXT, {
max_new_tokens: MAX_NEW_TOKENS,
});
const expected_length = PROMPT_LENGTH + MAX_NEW_TOKENS;
expect(outputs.dims.at(-1)).toEqual(expected_length);
},
MAX_TEST_EXECUTION_TIME,
);
it(
"min_length",
async () => {
const MIN_LENGTH = 4;
const outputs = await generate(model, tokenizer, DUMMY_TEXT, {
eos_token_id: [
18547, // min_length will suppress this token (generated by default)
16012, // stop at this token
],
min_length: MIN_LENGTH,
});
expect(outputs.tolist()).toEqual([[1n, 22172n, 31583n, 18824n, 16621n, 8136n, 16012n]]);
expect(outputs.dims.at(-1)).toBeGreaterThanOrEqual(MIN_LENGTH);
},
MAX_TEST_EXECUTION_TIME,
);
it(
"min_new_tokens",
async () => {
const MIN_NEW_TOKENS = 2;
const outputs = await generate(model, tokenizer, DUMMY_TEXT, {
eos_token_id: [
18547, // min_new_tokens will suppress this token (generated by default)
16012, // stop at this token
],
min_new_tokens: MIN_NEW_TOKENS,
});
expect(outputs.tolist()).toEqual([[1n, 22172n, 31583n, 18824n, 16621n, 8136n, 16012n]]);
expect(outputs.dims.at(-1)).toBeGreaterThanOrEqual(MIN_NEW_TOKENS);
},
MAX_TEST_EXECUTION_TIME,
);
it(
"do_sample (seeded)",
async () => {
// Seed 42: deterministic sampling
random.seed(42);
const outputs_seed42_a = await generate(model, tokenizer, DUMMY_TEXT, {
do_sample: true,
max_new_tokens: 10,
});
// Re-seed 42: must reproduce the same output
random.seed(42);
const outputs_seed42_b = await generate(model, tokenizer, DUMMY_TEXT, {
do_sample: true,
max_new_tokens: 10,
});
// Seed 123: different seed → different output
random.seed(123);
const outputs_seed123 = await generate(model, tokenizer, DUMMY_TEXT, {
do_sample: true,
max_new_tokens: 10,
});
const expected_seed42 = [[1n, 22172n, 28220n, 5345n, 27342n, 14352n, 24712n, 19249n, 24075n, 19934n, 8678n, 30868n]];
const expected_seed123 = [[1n, 22172n, 10131n, 867n, 12403n, 24755n, 16382n, 21742n, 24662n, 19120n, 22952n, 945n]];
expect(outputs_seed42_a.tolist()).toEqual(expected_seed42);
expect(outputs_seed42_b.tolist()).toEqual(expected_seed42);
expect(outputs_seed123.tolist()).toEqual(expected_seed123);
},
MAX_TEST_EXECUTION_TIME,
);
afterAll(async () => {
await model?.dispose();
}, MAX_MODEL_DISPOSE_TIME);
});
});
describe("Streamers", () => {
describe("decoder-only (Llama)", () => {
const model_id = "hf-internal-testing/tiny-random-LlamaForCausalLM";
let model, tokenizer;
let DUMMY_TOKEN_STREAM;
beforeAll(async () => {
model = await AutoModelForCausalLM.from_pretrained(model_id, DEFAULT_MODEL_OPTIONS);
tokenizer = await AutoTokenizer.from_pretrained(model_id);
DUMMY_TOKEN_STREAM = [
[12199n], // regular token ("hello")
[12199n], // regular token ("hello")
[22172n], // regular token (" hello")
[12199n], // regular token ("hello")
[13n], // regular token ("\n")
[12199n], // regular token ("hello")
[BigInt(tokenizer.bos_token_id)], // special token (BOS)
[12199n], // regular token ("hello")
];
}, MAX_MODEL_LOAD_TIME);
it(
"batch_size=1",
async () => {
const target_chunks = ["hello", /* Always flush after prompt. */ "erdingsdelete ", "melytabular ", "Stadiumoba ", "alcune ", "drug"];
const chunks = [];
const callback_function = (text) => {
chunks.push(text);
};
const streamer = new TextStreamer(tokenizer, { callback_function, skip_special_tokens: true });
const inputs = tokenizer("hello");
const outputs = await model.generate({
...inputs,
max_length: 10,
streamer,
});
expect(outputs.tolist()).toEqual([[1n, 22172n, 18547n, 8143n, 22202n, 9456n, 17213n, 15330n, 26591n, 15721n]]);
expect(chunks).toEqual(target_chunks);
},
MAX_TEST_EXECUTION_TIME,
);
it("special tokens are flushed immediately", async () => {
const chunks = [];
const callback_function = (text) => chunks.push(text);
const streamer = new TextStreamer(tokenizer, { callback_function, skip_special_tokens: false });
for (const tokens of DUMMY_TOKEN_STREAM) {
streamer.put([tokens]);
}
streamer.end();
expect(chunks).toEqual(["hello", /* Always flush after prompt. */ "hello ", "hellohello\n", "hello", "<s>", "hello"]);
});
it("special tokens are skipped with skip_special_tokens: true", async () => {
const chunks = [];
const callback_function = (text) => chunks.push(text);
const streamer = new TextStreamer(tokenizer, { callback_function, skip_special_tokens: true });
for (const tokens of DUMMY_TOKEN_STREAM) {
streamer.put([tokens]);
}
streamer.end();
expect(chunks).toEqual(["hello", /* Always flush after prompt. */ "hello ", "hellohello\n", "hellohello"]);
});
afterAll(async () => {
await model?.dispose();
}, MAX_MODEL_DISPOSE_TIME);
});
describe("decoder-only (GPT-OSS)", () => {
const model_id = "onnx-community/gpt-oss-20b-ONNX";
let tokenizer;
let TOKEN_STREAM;
beforeAll(async () => {
tokenizer = await AutoTokenizer.from_pretrained(model_id);
TOKEN_STREAM = [
// Prompt tokens:
[200006n, 17360n, 200008n, 3575n, 553n, 17554n, 162016n, 11n, 261n, 4410n, 6439n, 2359n, 22203n, 656n, 7788n, 17527n, 558n, 87447n, 100594n, 25n, 220n, 1323n, 19n, 12n, 3218n, 198n, 6576n, 3521n, 25n, 220n, 1323n, 21n, 12n, 3286n, 12n, 702n, 279n, 30377n, 289n, 25n, 14093n, 279n, 2n, 13888n, 18403n, 25n, 8450n, 11n, 49159n, 11n, 1721n, 13n, 21030n, 2804n, 413n, 7360n, 395n, 1753n, 3176n, 13n, 200007n, 200006n, 77944n, 200008n, 2n, 68406n, 279n, 3575n, 553n, 261n, 10297n, 29186n, 13n, 200007n, 200006n, 1428n, 200008n, 10930n, 668n, 261n, 41339n, 1078n, 19121n, 25392n, 13n, 200007n, 200006n, 173781n],
// Generated tokens:
// <|channel|>analysis<|message|>
[200005n],
[35644n],
[200008n],
// We need to write a poem about Machine Learning. Should be creative, maybe with some technical references but poetic. Let's produce a nice poem.
[2167n],
[1309n],
[316n],
[5067n],
[261n],
[41339n],
[1078n],
[19121n],
[25392n],
[13n],
[18057n],
[413n],
[12879n],
[11n],
[10112n],
[483n],
[1236n],
[11814n],
[25382n],
[889n],
[114824n],
[13n],
[41021n],
[10635n],
[261n],
[7403n],
[41339n],
[13n],
// <|end|><|start|>assistant<|channel|>final<|message|>
[200007n],
[200006n],
[173781n],
[200005n],
[17196n],
[200008n],
// **When the Machine Learns to Dream**
//
// In a quiet room of humming silicon,
// Where electrons trace their silent paths,
// A
[410n],
[5958n],
[290n],
[19121n],
[103596n],
[6097n],
[316n],
[24243n],
[91587n],
[637n],
[261n],
[15095n],
[3435n],
[328n],
[147045n],
[68837n],
[11n],
[4066n],
[11977n],
[100085n],
[21523n],
[1043n],
[37716n],
[23373n],
[11n],
[4066n],
[32n],
];
}, MAX_MODEL_LOAD_TIME);
it("special tokens are flushed immediately", async () => {
const chunks = [];
const callback_function = (text) => chunks.push(text);
const streamer = new TextStreamer(tokenizer, { callback_function, skip_special_tokens: false });
for (const tokens of TOKEN_STREAM) {
streamer.put([tokens]);
}
streamer.end();
const TARGET = [
// Prompt
"<|start|>system<|message|>You are ChatGPT, a large language model trained by OpenAI.\nKnowledge cutoff: 2024-06\nCurrent date: 2026-02-10\n\nReasoning: medium\n\n# Valid channels: analysis, commentary, final. Channel must be included for every message.<|end|><|start|>developer<|message|># Instructions\n\nYou are a helpful assistant.<|end|><|start|>user<|message|>Write me a poem about Machine Learning.<|end|><|start|>assistant",
// Generated tokens
"<|channel|>",
"analysis",
"<|message|>",
"We ",
"need ",
"to ",
"write ",
"a ",
"poem ",
"about ",
"Machine ",
"Learning. ",
"Should ",
"be ",
"creative, ",
"maybe ",
"with ",
"some ",
"technical ",
"references ",
"but ",
"poetic. ",
"Let's ",
"produce ",
"a ",
"nice ",
"poem.",
"<|end|>",
"<|start|>",
"assistant",
"<|channel|>",
"final",
"<|message|>",
"**When ",
"the ",
"Machine ",
"Learns ",
"to ",
"Dream**\n\n",
"In ",
"a ",
"quiet ",
"room ",
"of ",
"humming ",
"silicon, \n",
"Where ",
"electrons ",
"trace ",
"their ",
"silent ",
"paths, \n",
"A",
];
expect(chunks).toEqual(TARGET);
});
});
});
describe("Dynamic Cache", () => {
it("should update and get sequence length correctly", () => {
const cache = new DynamicCache();
expect(cache.get_seq_length()).toEqual(0);
cache.update({
"past_key_values.0.key": full([1, 2, 16, 32], 0.0),
"past_key_values.0.value": full([1, 2, 16, 32], 0.0),
});
expect(cache.get_seq_length()).toEqual(16);
});
});
describe("PKV caching", () => {
describe("LlamaForCausalLM", () => {
const model_id = "hf-internal-testing/tiny-random-LlamaForCausalLM";
/** @type {LlamaForCausalLM} */
let model;
/** @type {LlamaTokenizer} */
let tokenizer;
beforeAll(async () => {
model = await LlamaForCausalLM.from_pretrained(model_id, DEFAULT_MODEL_OPTIONS);
tokenizer = await LlamaTokenizer.from_pretrained(model_id);
}, MAX_MODEL_LOAD_TIME);
it(
"batch_size=1",
async () => {
const inputs = tokenizer("1");
// Generate first sequence w/o PKV
// NOTE: `return_dict_in_generate=true` is required to get PKV
const { past_key_values, sequences } = await model.generate({
...inputs,
max_new_tokens: 5,
do_sample: false,
return_dict_in_generate: true,
});
// Update output with new text
const decoded = tokenizer.batch_decode(sequences, {
skip_special_tokens: false,
})[0];
const new_inputs = tokenizer(decoded + "2", {
add_special_tokens: false,
});
// Run w/o PKV
const generated_ids = await model.generate({
...new_inputs,
max_new_tokens: 3,
do_sample: false,
});
// Run w/ PKV
const generated_ids_pkv = await model.generate({
...new_inputs,
past_key_values,
max_new_tokens: 3,
do_sample: false,
});
const target = [[1n, 259n, 29896n, 24959n, 22063n, 17192n, 12189n, 22468n, 29906n, 3399n, 24823n, 26470n]];
expect(generated_ids.tolist()).toEqual(target);
expect(generated_ids_pkv.tolist()).toEqual(target);
},
MAX_TEST_EXECUTION_TIME,
);
afterAll(async () => {
await model?.dispose();
}, MAX_MODEL_DISPOSE_TIME);
});
describe("LlamaForCausalLM (onnxruntime-genai)", () => {
const model_id = "onnx-internal-testing/tiny-random-LlamaForCausalLM-GQA";
/** @type {LlamaForCausalLM} */
let model;
/** @type {LlamaTokenizer} */
let tokenizer;
beforeAll(async () => {
model = await LlamaForCausalLM.from_pretrained(model_id, DEFAULT_MODEL_OPTIONS);
tokenizer = await LlamaTokenizer.from_pretrained(model_id);
}, MAX_MODEL_LOAD_TIME);
it(
"batch_size=1",
async () => {
const inputs = tokenizer("1");
// Generate first sequence w/o PKV
// NOTE: `return_dict_in_generate=true` is required to get PKV
const { past_key_values, sequences } = await model.generate({
...inputs,
max_new_tokens: 5,
do_sample: false,
return_dict_in_generate: true,
});
// Update output with new text
const decoded = tokenizer.batch_decode(sequences, {
skip_special_tokens: false,
})[0];
const new_inputs = tokenizer(decoded + "2", {
add_special_tokens: false,
});
// Run w/o PKV
const generated_ids = await model.generate({
...new_inputs,
max_new_tokens: 3,
do_sample: false,
});
// Run w/ PKV
const generated_ids_pkv = await model.generate({
...new_inputs,
past_key_values,
max_new_tokens: 3,
do_sample: false,
});
const target = [[128000n, 16n, 34732n, 98805n, 116404n, 68265n, 99392n, 17n, 21855n, 60933n, 14285n]];
expect(generated_ids.tolist()).toEqual(target);
expect(generated_ids_pkv.tolist()).toEqual(target);
},
MAX_TEST_EXECUTION_TIME,
);
afterAll(async () => {
await model?.dispose();
}, MAX_MODEL_DISPOSE_TIME);
});
});