Skip to content

Commit ed84c15

Browse files
authored
Improve tokenizer types based on input function parameters (#1641)
* Add tokenizer types tests * update tests * Improve types * add back docs * Update preprocess.js * fix build
1 parent 8d615c5 commit ed84c15

7 files changed

Lines changed: 233 additions & 54 deletions

File tree

packages/transformers/docs/plugins/preprocess.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,47 @@ function stripLeadingGenericParams(expr) {
2525
return depth === 0 ? expr.slice(i) : expr;
2626
}
2727

28+
function hasTopLevelConditional(expr) {
29+
let angle = 0,
30+
paren = 0,
31+
brace = 0,
32+
bracket = 0;
33+
34+
for (let i = 0; i < expr.length; ++i) {
35+
const ch = expr[i];
36+
if (ch === "<") angle++;
37+
else if (ch === ">") angle = Math.max(0, angle - 1);
38+
else if (ch === "(") paren++;
39+
else if (ch === ")") paren = Math.max(0, paren - 1);
40+
else if (ch === "{") brace++;
41+
else if (ch === "}") brace = Math.max(0, brace - 1);
42+
else if (ch === "[") bracket++;
43+
else if (ch === "]") bracket = Math.max(0, bracket - 1);
44+
else if (ch === "?" && angle === 0 && paren === 0 && brace === 0 && bracket === 0) {
45+
return true;
46+
}
47+
}
48+
return false;
49+
}
50+
2851
function transformType(expr) {
2952
let result = expr,
3053
prev = "";
3154
while (result !== prev) {
3255
prev = result;
3356
result = stripLeadingGenericParams(result); // <T extends X>(...) -> (...)
57+
if (/^Promise<function\s*\(/.test(result)) {
58+
result = "Promise.<Function>";
59+
continue;
60+
}
61+
if (/^function\s*\(/.test(result)) {
62+
result = "Function";
63+
continue;
64+
}
65+
if (hasTopLevelConditional(result)) {
66+
result = "any";
67+
continue;
68+
}
3469
result = result
3570
.replace(/import\([^)]+\)((?:\.\w+)*)/g, (_, p) => p?.slice(1) || "any") // import() -> Type
3671
.replace(/\w+\[['"][^\]]+['"]\]\s+extends\s+[^}]+/g, "any") // X['p'] extends ... -> any
@@ -53,7 +88,6 @@ function transformType(expr) {
5388
.replace(/(\w+)\[\]/g, "Array") // T[] -> Array (simple)
5489
.replace(/\[\w+\]/g, "Array") // [T] single-element tuple -> Array
5590
.replace(/\[[^\[\]]*,[^\[\]]*\]/g, "Array") // tuples with commas -> Array
56-
.replace(/\w+\s+extends\s+[^?]+\?\s*[^:]+\s*:\s*[^,}>)]+/g, "any") // conditionals -> any
5791
.replace(/\bnew\s+([A-Z]\w*)\b/g, "$1") // new Type -> Type
5892
.replace(/,?\s*\[\s*\w+\s*:\s*\w+\s*\]\s*:\s*\w+/g, "") // [key: string]: any -> (removed)
5993
.replace(/\(\s*(\w+)\s*&\s*\{\s*\}\s*\)/g, "$1") // (string & {}) -> string

packages/transformers/src/generation/parameters.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* @property {import('./streamers.js').BaseStreamer} [streamer=null] (`BaseStreamer`, *optional*):
2929
* Streamer object that will be used to stream the generated sequences. Generated tokens are passed
3030
* through `streamer.put(token_ids)` and the streamer is responsible for any further processing.
31-
* @property {number[]} [decoder_input_ids=null] (`number[]`, *optional*):
31+
* @property {number[]|import('../utils/tensor.js').Tensor} [decoder_input_ids=null] (`number[]` or `Tensor`, *optional*):
3232
* If the model is an encoder-decoder model, this argument is used to pass the `decoder_input_ids`.
3333
* @property {import('../cache_utils.js').DynamicCache | null} [past_key_values=null] (`DynamicCache`, *optional*):
3434
* A cache object that stores previously computed key/value states. When provided, the model will

packages/transformers/src/models/whisper/modeling_whisper.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { cat, mean, Tensor, stack, std_mean } from '../../utils/tensor.js';
22
import { PreTrainedModel } from '../modeling_utils.js';
33
import { WhisperGenerationConfig } from './generation_whisper.js';
44
import { whisper_language_to_code } from './common_whisper.js';
5+
import { prepareTensorForDecode } from '../../tokenization_utils.js';
56
import {
67
LogitsProcessorList,
78
SuppressTokensAtBeginLogitsProcessor,
@@ -116,7 +117,9 @@ export class WhisperForConditionalGeneration extends WhisperPreTrainedModel {
116117
}) {
117118
generation_config = this._prepare_generation_config(generation_config, kwargs);
118119

119-
const init_tokens = kwargs.decoder_input_ids ?? this._retrieve_init_tokens(generation_config);
120+
const init_tokens = kwargs.decoder_input_ids instanceof Tensor
121+
? prepareTensorForDecode(kwargs.decoder_input_ids)
122+
: kwargs.decoder_input_ids ?? this._retrieve_init_tokens(generation_config);
120123

121124
if (generation_config.return_timestamps) {
122125
logits_processor ??= new LogitsProcessorList();

packages/transformers/src/pipelines/text-generation.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,15 @@ export class TextGenerationPipeline
124124
// If the input is a chat, we need to apply the chat template
125125
inputs = /** @type {string[]} */ (
126126
/** @type {Chat[]} */ (texts).map((x) =>
127-
this.tokenizer.apply_chat_template(x, {
128-
tokenize: false,
129-
add_generation_prompt: true,
130-
...tokenizer_kwargs,
131-
}),
127+
/** @type {string} */ (
128+
/** @type {unknown} */ (
129+
this.tokenizer.apply_chat_template(x, {
130+
tokenize: false,
131+
add_generation_prompt: true,
132+
...tokenizer_kwargs,
133+
})
134+
)
135+
),
132136
)
133137
);
134138
// Chat template handles these already

packages/transformers/src/tokenization_utils.js

Lines changed: 90 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,68 @@ function getSpecialTokens(tokenizer) {
167167
return special;
168168
}
169169

170-
export class PreTrainedTokenizer extends Callable {
170+
/**
171+
* @template {string|string[]} TText
172+
* @typedef {TText extends string ? number[] : number[][]} BatchEncodingArrayItem
173+
*/
174+
175+
/**
176+
* @template {string|string[]} TText
177+
* @template {boolean} [TReturnTensor=true]
178+
* @typedef {TReturnTensor extends true ? Tensor : BatchEncodingArrayItem<TText>} BatchEncodingItem
179+
*/
180+
181+
/**
182+
* @template TItem
183+
* @typedef {Object} BatchEncoding
184+
* @property {TItem} input_ids List of token ids to be fed to a model.
185+
* @property {TItem} attention_mask List of indices specifying which tokens should be attended to by the model.
186+
* @property {TItem} [token_type_ids] List of token type ids to be fed to a model.
187+
*/
188+
189+
/**
190+
* @template {string|string[]} TText
191+
* @template {boolean} [TReturnTensor=true]
192+
* @typedef {Object} TokenizerCallOptions
193+
* @property {TText extends string ? string|null : string[]|null} [text_pair=null] Optional second sequence to be encoded. If set, must be the same type as text.
194+
* @property {boolean|'max_length'} [padding=false] Whether to pad the input sequences.
195+
* @property {boolean} [add_special_tokens=true] Whether or not to add the special tokens associated with the corresponding model.
196+
* @property {boolean|null} [truncation=null] Whether to truncate the input sequences.
197+
* @property {number|null} [max_length=null] Maximum length of the returned list and optionally padding length.
198+
* @property {TReturnTensor} [return_tensor=true] Whether to return the results as Tensors or arrays.
199+
* @property {boolean|null} [return_token_type_ids=null] Whether to return the token type ids.
200+
*/
201+
202+
/**
203+
* @typedef {<TText extends string | string[], TReturnTensor extends boolean = true>(text: TText, options?: TokenizerCallOptions<TText, TReturnTensor>) => BatchEncoding<BatchEncodingItem<TText, TReturnTensor>>} PreTrainedTokenizerCallback
204+
*/
205+
206+
/**
207+
* @template {boolean} [TTokenize=true]
208+
* @template {boolean} [TReturnTensor=true]
209+
* @template {boolean} [TReturnDict=true]
210+
* @typedef {Object} ApplyChatTemplateOptions
211+
* @property {string|null} [chat_template=null] A Jinja template to use for this conversion.
212+
* @property {Object[]|null} [tools=null] A list of tools (callable functions) that will be accessible to the model.
213+
* @property {Record<string, string>[]|null} [documents=null] Documents that will be accessible to the model.
214+
* @property {boolean} [add_generation_prompt=false] Whether to end the prompt with the token(s) that indicate the start of an assistant message.
215+
* @property {TTokenize} [tokenize=true] Whether to tokenize the output. If false, the output will be a string.
216+
* @property {boolean} [padding=false] Whether to pad sequences to the maximum length. Has no effect if tokenize is false.
217+
* @property {boolean} [truncation=false] Whether to truncate sequences to the maximum length. Has no effect if tokenize is false.
218+
* @property {number|null} [max_length=null] Maximum length (in tokens) to use for padding or truncation. Has no effect if tokenize is false.
219+
* @property {TReturnTensor} [return_tensor=true] Whether to return the output as a Tensor or an Array. Has no effect if tokenize is false.
220+
* @property {TReturnDict} [return_dict=true] Whether to return a dictionary with named outputs. Has no effect if tokenize is false.
221+
* @property {Object} [tokenizer_kwargs={}] Additional options to pass to the tokenizer.
222+
*/
223+
224+
/**
225+
* @template {boolean} [TTokenize=true]
226+
* @template {boolean} [TReturnTensor=true]
227+
* @template {boolean} [TReturnDict=true]
228+
* @typedef {TTokenize extends false ? string : TReturnDict extends false ? BatchEncodingItem<string, TReturnTensor> : BatchEncoding<BatchEncodingItem<string, TReturnTensor>>} ApplyChatTemplateReturn
229+
*/
230+
231+
export class PreTrainedTokenizer extends /** @type {new (tokenizerJSON: Object, tokenizerConfig: Object) => PreTrainedTokenizerCallback} */ (Callable) {
171232
return_token_type_ids = false;
172233

173234
padding_side = 'right';
@@ -281,43 +342,23 @@ export class PreTrainedTokenizer extends Callable {
281342
}
282343
}
283344

284-
/**
285-
* @typedef {number[]|number[][]|Tensor} BatchEncodingItem
286-
*
287-
* @typedef {Object} BatchEncoding Holds the output of the tokenizer's call function.
288-
* @property {BatchEncodingItem} input_ids List of token ids to be fed to a model.
289-
* @property {BatchEncodingItem} attention_mask List of indices specifying which tokens should be attended to by the model.
290-
* @property {BatchEncodingItem} [token_type_ids] List of token type ids to be fed to a model.
291-
*/
292-
293345
/**
294346
* Encode/tokenize the given text(s).
295-
* @param {string|string[]} text The text to tokenize.
296-
* @param {Object} options An optional object containing the following properties:
297-
* @param {string|string[]} [options.text_pair=null] Optional second sequence to be encoded. If set, must be the same type as text.
298-
* @param {boolean|'max_length'} [options.padding=false] Whether to pad the input sequences.
299-
* @param {boolean} [options.add_special_tokens=true] Whether or not to add the special tokens associated with the corresponding model.
300-
* @param {boolean|null} [options.truncation=null] Whether to truncate the input sequences.
301-
* @param {number|null} [options.max_length=null] Maximum length of the returned list and optionally padding length.
302-
* @param {boolean} [options.return_tensor=true] Whether to return the results as Tensors or arrays.
303-
* @param {boolean|null} [options.return_token_type_ids=null] Whether to return the token type ids.
304-
* @returns {BatchEncoding} Object to be passed to the model.
347+
* @template {string|string[]} TText
348+
* @template {boolean} [TReturnTensor=true]
349+
* @param {TText} text The text to tokenize.
350+
* @param {TokenizerCallOptions<TText, TReturnTensor>} [options] Additional tokenization options.
351+
* @returns {BatchEncoding<BatchEncodingItem<TText, TReturnTensor>>} Object to be passed to the model.
305352
*/
306353
_call(
307354
// Required positional arguments
308355
text,
309-
310-
// Optional keyword arguments
311-
{
312-
text_pair = null,
313-
add_special_tokens = true,
314-
padding = false,
315-
truncation = null,
316-
max_length = null,
317-
return_tensor = true, // Different to HF
318-
return_token_type_ids = null,
319-
} = {},
356+
options = {},
320357
) {
358+
const { text_pair = null, add_special_tokens = true, padding = false, return_token_type_ids = null } = options;
359+
let { truncation = null, max_length = null } = options;
360+
const return_tensor = /** @type {TReturnTensor} */ (options.return_tensor ?? true); // Different to HF
361+
321362
const isBatched = Array.isArray(text);
322363

323364
let encodedTokens;
@@ -457,7 +498,7 @@ export class PreTrainedTokenizer extends Callable {
457498
}
458499
}
459500

460-
return /** @type {BatchEncoding} */ (result);
501+
return /** @type {BatchEncoding<BatchEncodingItem<TText, TReturnTensor>>} */ (result);
461502
}
462503

463504
/**
@@ -662,7 +703,10 @@ export class PreTrainedTokenizer extends Callable {
662703
*
663704
* @param {Message[]} conversation A list of message objects with `"role"` and `"content"` keys,
664705
* representing the chat history so far.
665-
* @param {Object} options An optional object containing the following properties:
706+
* @template {boolean} [TTokenize=true]
707+
* @template {boolean} [TReturnTensor=true]
708+
* @template {boolean} [TReturnDict=true]
709+
* @param {Object} [options] An optional object containing the following properties:
666710
* @param {string|null} [options.chat_template=null] A Jinja template to use for this conversion. If
667711
* this is not passed, the model's chat template will be used instead.
668712
* @param {Object[]} [options.tools=null]
@@ -681,33 +725,34 @@ export class PreTrainedTokenizer extends Callable {
681725
* the start of an assistant message. This is useful when you want to generate a response from the model.
682726
* Note that this argument will be passed to the chat template, and so it must be supported in the
683727
* template for this argument to have any effect.
684-
* @param {boolean} [options.tokenize=true] Whether to tokenize the output. If false, the output will be a string.
728+
* @param {TTokenize} [options.tokenize=true] Whether to tokenize the output. If false, the output will be a string.
685729
* @param {boolean} [options.padding=false] Whether to pad sequences to the maximum length. Has no effect if tokenize is false.
686730
* @param {boolean} [options.truncation=false] Whether to truncate sequences to the maximum length. Has no effect if tokenize is false.
687731
* @param {number|null} [options.max_length=null] Maximum length (in tokens) to use for padding or truncation. Has no effect if tokenize is false.
688732
* If not specified, the tokenizer's `max_length` attribute will be used as a default.
689-
* @param {boolean} [options.return_tensor=true] Whether to return the output as a Tensor or an Array. Has no effect if tokenize is false.
690-
* @param {boolean} [options.return_dict=true] Whether to return a dictionary with named outputs. Has no effect if tokenize is false.
733+
* @param {TReturnTensor} [options.return_tensor=true] Whether to return the output as a Tensor or an Array. Has no effect if tokenize is false.
734+
* @param {TReturnDict} [options.return_dict=true] Whether to return a dictionary with named outputs. Has no effect if tokenize is false.
691735
* @param {Object} [options.tokenizer_kwargs={}] Additional options to pass to the tokenizer.
692-
* @returns {string | Tensor | number[]| number[][]|BatchEncoding} The tokenized output.
736+
* @returns {ApplyChatTemplateReturn<TTokenize, TReturnTensor, TReturnDict>} The tokenized output.
693737
*/
694738
apply_chat_template(
695739
conversation,
696-
{
740+
options = /** @type {ApplyChatTemplateOptions<TTokenize, TReturnTensor, TReturnDict>} */ ({}),
741+
) {
742+
let {
697743
tools = null,
698744
documents = null,
699745
chat_template = null,
700746
add_generation_prompt = false,
701-
tokenize = true,
747+
tokenize = /** @type {TTokenize} */ (true),
702748
padding = false,
703749
truncation = false,
704750
max_length = null,
705-
return_tensor = true,
706-
return_dict = true,
751+
return_tensor = /** @type {TReturnTensor} */ (true),
752+
return_dict = /** @type {TReturnDict} */ (true),
707753
tokenizer_kwargs = {},
708754
...kwargs
709-
} = {},
710-
) {
755+
} = options;
711756
chat_template = this.get_chat_template({ chat_template, tools });
712757

713758
if (typeof chat_template !== 'string') {
@@ -748,10 +793,10 @@ export class PreTrainedTokenizer extends Callable {
748793
return_tensor,
749794
...tokenizer_kwargs,
750795
});
751-
return return_dict ? out : out.input_ids;
796+
return /** @type {ApplyChatTemplateReturn<TTokenize, TReturnTensor, TReturnDict>} */ (return_dict ? out : out.input_ids);
752797
}
753798

754-
return rendered;
799+
return /** @type {ApplyChatTemplateReturn<TTokenize, TReturnTensor, TReturnDict>} */ (rendered);
755800
}
756801
}
757802

packages/transformers/tests/types.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function getDiagnosticsFromSource(source) {
4040

4141
describe("TypeScript compilation succeeds", () => {
4242
const DIR = "tests/types/";
43-
const FILES = ["pipelines.ts", "cache.ts"];
43+
const FILES = ["pipelines.ts", "cache.ts", "tokenizers.ts"];
4444
for (const file of FILES) {
4545
it(`compiles ${file} without errors`, () => {
4646
const diagnostics = getDiagnostics(`${DIR}${file}`);

0 commit comments

Comments
 (0)