Coming from transformers.js
Kiln implements transformers.js's pipelines and its lower-level API under the same names, with the same option names and output shapes. Most code moves over by changing the import and the model id. This page lists everything that differs.
The three steps
-
Change the import.
- import { pipeline, AutoTokenizer } from "@huggingface/transformers"; + import { pipeline, AutoTokenizer } from "kiln-webgpu"; -
Point at the original repo. Kiln reads a model's own weights (safetensors, pytorch_model.bin or GGUF), not ONNX exports. Most
Xenova/…andonnx-community/…repos are conversions of a repo you can name directly:The ONNX repo's model card usually names the original under "Base model".transformers.js Kiln Xenova/all-MiniLM-L6-v2sentence-transformers/all-MiniLM-L6-v2Xenova/distilbert-base-uncased-finetuned-sst-2-englishdistilbert/distilbert-base-uncased-finetuned-sst-2-englishXenova/bert-base-NERdslim/bert-base-NERXenova/whisper-tiny.enopenai/whisper-tiny.enXenova/vit-base-patch16-224google/vit-base-patch16-224Xenova/clip-vit-base-patch32openai/clip-vit-base-patch32onnx-community/Qwen2.5-0.5B-InstructQwen/Qwen2.5-0.5B-InstructHuggingFaceTB/SmolLM2-135M-Instruct(its onnx/ folder)the same repo (its safetensors) a GGUF build (not supported) org/repo/model-Q4_K_M.gguf - Check the differences below that apply to the APIs you use.
Load options
dtype: Kiln quantizes at load, so one repo serves every precision. 4-bit names (q4,q4f16,bnb4,int4) load as q4, 8-bit names (q8,int8,uint8) as q8,fp16/fp32as f32. Encoders default to f32, LLMs to q4.device:"auto"(default) uses WebGPU where there is one, else the CPU;"webgpu"/"gpu"requires WebGPU;"wasm"/"cpu"forces the CPU. transformers.js defaults to WASM; Kiln defaults to the GPU.progress_callbackreceivesinitiate,progressanddoneper file, thenready.revisionworks;cache_dir,local_files_onlyandsession_optionsdo not apply (Kiln caches in OPFS itself).env.remoteHostpoints Kiln at a Hub mirror; otherenvsettings are accepted and ignored.
Pipelines
Task names, aliases (sentiment-analysis, ner, embeddings, text-to-speech), call options and output shapes follow transformers.js. Differences:
- Streamers receive text, not token ids.
new TextStreamer(tokenizer, { callback_function })works unchanged; a custom streamer needsput(text)andend(). - text-generation does not read generation_config.json: greedy unless
do_sample: true, and withoutmax_new_tokensa reply runs until the model ends its turn. (AutoModelForCausalLM.generate()does read it.) - Encoder batches run without padding:
feature-extractionwithpooling: "none"returns zeros past a shorter input's last token, where transformers.js holds the padded rows. Pooled outputs are unaffected. - Images are preprocessed as Python does it (PIL's resampling), not by a canvas resize, so pixel values, and slightly the scores, differ from transformers.js's (they match Python's).
- Long audio: past 30 s, Whisper is chunked in 30 s windows by default; transformers.js keeps only the first 30 s unless
chunk_length_sis set. - Kokoro takes a phonemizer function (
phonemizefrom thephonemizerpackage), since espeak-ng is GPL and not bundled. - Kiln's own options can be passed too:
format(schema-constrained output),tools,signal,adapter.
Tokenizers, models and processors
| transformers.js | In Kiln |
|---|---|
AutoTokenizer | Yes: call, encode, decode, batch_decode, tokenize, padding, truncation, apply_chat_template. Ids equal transformers.js's on the 31 repos compared. |
AutoModel | BERT, DistilBERT, RoBERTa (last_hidden_state); ViT, DeiT, DINOv2, ResNet, ConvNeXt; CLIP. |
AutoModelFor… | SequenceClassification, TokenClassification, QuestionAnswering, MaskedLM, ImageClassification, CausalLM. |
CLIPModel, CLIPTextModelWithProjection, CLIPVisionModelWithProjection | Yes. |
model.generate() | For causal LMs: ids in, ids out, sampling options, streamers, generation_config defaults. Calling a causal LM directly for logits is not supported; llm.scoreChoices() scores continuations instead. |
AutoProcessor, AutoImageProcessor, AutoFeatureExtractor | Images (pixel_values), Whisper (input_features), wav2vec2 (input_values). |
RawImage, read_audio, Tensor, cos_sim | Yes. read_audio decodes as transformers.js does. Tensor carries data, dims and tolist(), without transformers.js's tensor math. |
| Other model classes and architectures | Not yet: use the pipeline for the task, or open an issue for the model. |
Models run in Kiln's worker and come back as tensors with transformers.js's names and shapes. Each row of a padded batch runs without its padding, so padded positions hold zeros.
Before and after
Embeddings
import { pipeline } from "kiln-webgpu"; // was "@huggingface/transformers"
const extractor = await pipeline("feature-extraction", "sentence-transformers/all-MiniLM-L6-v2"); // was Xenova/all-MiniLM-L6-v2
const output = await extractor(["Hello world", "Hi"], { pooling: "mean", normalize: true });
output.tolist();
Chat with streaming
import { pipeline, TextStreamer } from "kiln-webgpu";
const generator = await pipeline("text-generation", "Qwen/Qwen2.5-0.5B-Instruct", { dtype: "q4" });
const streamer = new TextStreamer(generator.tokenizer, { skip_prompt: true, callback_function: (t) => out.append(t) });
await generator(messages, { max_new_tokens: 256, streamer });
Tokenizer and model
import { AutoTokenizer, AutoModelForSequenceClassification } from "kiln-webgpu";
const id = "distilbert/distilbert-base-uncased-finetuned-sst-2-english";
const tokenizer = await AutoTokenizer.from_pretrained(id);
const model = await AutoModelForSequenceClassification.from_pretrained(id);
const { logits } = await model(tokenizer(["I love it", "I hate it"], { padding: true }));
What you get on top
- Structured output for any LLM, with text or image prompts.
- Prompt-prefix caching across calls and reloads; draft tokens for faster decoding.
- LoRA adapters, retrieval and LLM tasks with your own labels.
- GGUF files and models without an ONNX export.
- On the same GPU, faster end to end on every pipeline measured so far. The benchmarks also list where it is slower: loading from the cache, and AutoModel masked-LM logits.
What is missing
- Many architectures. transformers.js runs any ONNX graph; Kiln implements each family itself, and the list is shorter.
- Node.js loading from local folders (
env.localModelPath); Kiln loads from the Hub or a mirror. - transformers.js's tensor operations (
mean,slice, …) on returned tensors. - Batched decoding of several prompts at once: Kiln runs them one after another.