ONNX export of
LFM2.5-1.2B-Thinking for cross-platform inference.
LFM2.5-Thinking is a reasoning model that generates step-by-step thinking before producing final answers. The model outputs its reasoning process within <think>...</think> tags, followed by the final response. This approach improves accuracy on complex tasks like math, coding, and logical reasoning.
onnx/
├── model.onnx # FP32
├── model_fp16.onnx # FP16
├── model_q4.onnx # Q4 (recommended)
└── model_q8.onnx # Q8
1 pip install onnxruntime transformers numpy huggingface_hub
2 # or with GPU support:
3 pip install onnxruntime-gpu transformers numpy huggingface_hub
1 import re
2
3 import numpy as np
4 import onnxruntime as ort
5 from huggingface_hub import hf_hub_download
6 from transformers import AutoTokenizer
7
8 # Download model (Q4 recommended)
9 model_id = "LiquidAI/LFM2.5-1.2B-Thinking-ONNX"
10 model_path = hf_hub_download ( model_id , "onnx/model_q4.onnx" )
11 data_path = hf_hub_download ( model_id , "onnx/model_q4.onnx_data" )
12
13 # Load model and tokenizer
14 session = ort . InferenceSession ( model_path )
15 tokenizer = AutoTokenizer . from_pretrained ( model_id , trust_remote_code = True )
16
17 # Prepare chat input
18 messages = [ { "role" : "user" , "content" : "What is 25 * 37?" } ]
19 prompt = tokenizer . apply_chat_template ( messages , tokenize = False , add_generation_prompt = True )
20 input_ids = np . array ( [ tokenizer . encode ( prompt , add_special_tokens = False ) ] , dtype = np . int64 )
21
22 # Initialize KV cache
23 ONNX_DTYPE = { "tensor(float)" : np . float32 , "tensor(float16)" : np . float16 , "tensor(int64)" : np . int64 }
24 cache = { }
25 for inp in session . get_inputs ( ) :
26 if inp . name in { "input_ids" , "attention_mask" , "position_ids" } :
27 continue
28 shape = [ d if isinstance ( d , int ) else 1 for d in inp . shape ]
29 for i , d in enumerate ( inp . shape ) :
30 if isinstance ( d , str ) and "sequence" in d . lower ( ) :
31 shape [ i ] = 0
32 cache [ inp . name ] = np . zeros ( shape , dtype = ONNX_DTYPE . get ( inp . type , np . float32 ) )
33
34 # Check if model uses position_ids
35 input_names = { inp . name for inp in session . get_inputs ( ) }
36 use_position_ids = "position_ids" in input_names
37
38 # Generate tokens
39 seq_len = input_ids . shape [ 1 ]
40 generated_tokens = [ ]
41
42 for step in range ( 512 ) : # max tokens (reasoning may need more tokens)
43 if step == 0 :
44 ids = input_ids
45 pos = np . arange ( seq_len , dtype = np . int64 ) . reshape ( 1 , - 1 )
46 else :
47 ids = np . array ( [ [ generated_tokens [ - 1 ] ] ] , dtype = np . int64 )
48 pos = np . array ( [ [ seq_len + len ( generated_tokens ) - 1 ] ] , dtype = np . int64 )
49
50 attn_mask = np . ones ( ( 1 , seq_len + len ( generated_tokens ) ) , dtype = np . int64 )
51 feed = { "input_ids" : ids , "attention_mask" : attn_mask , ** cache }
52 if use_position_ids :
53 feed [ "position_ids" ] = pos
54
55 outputs = session . run ( None , feed )
56 next_token = int ( np . argmax ( outputs [ 0 ] [ 0 , - 1 ] ) )
57 generated_tokens . append ( next_token )
58
59 # Update cache
60 for i , out in enumerate ( session . get_outputs ( ) [ 1 : ] , 1 ) :
61 name = out . name . replace ( "present_conv" , "past_conv" ) . replace ( "present." , "past_key_values." )
62 if name in cache :
63 cache [ name ] = outputs [ i ]
64
65 if next_token == tokenizer . eos_token_id :
66 break
67
68 # Parse thinking and response
69 full_response = tokenizer . decode ( generated_tokens , skip_special_tokens = True )
70 think_match = re . search ( r"<think>(.*?)</think>" , full_response , re . DOTALL )
71 if think_match :
72 thinking = think_match . group ( 1 ) . strip ( )
73 answer = full_response [ think_match . end ( ) : ] . strip ( )
74 print ( f"Thinking:\n { thinking } \n" )
75 print ( f"Answer:\n { answer } " )
76 else :
77 print ( full_response )
WebGPU is required for browser inference. To enable:
1 import * as ort from "onnxruntime-web/webgpu" ;
2 import { AutoTokenizer } from "@huggingface/transformers" ;
3
4 // Check WebGPU availability
5 if ( ! navigator . gpu ) {
6 throw new Error ( "WebGPU not available. Enable at chrome://flags/#enable-unsafe-webgpu" ) ;
7 }
8 const adapter = await navigator . gpu . requestAdapter ( ) ;
9 if ( ! adapter ) {
10 throw new Error ( "WebGPU adapter not found. Check chrome://gpu for status." ) ;
11 }
12
13 ort . env . wasm . numThreads = 1 ;
14
15 const modelId = "LiquidAI/LFM2.5-1.2B-Thinking-ONNX" ;
16 const modelBase = ` https://huggingface.co/ ${ modelId } /resolve/main ` ;
17
18 // Load tokenizer
19 const tokenizer = await AutoTokenizer . from_pretrained ( modelId ) ;
20
21 // Load ONNX session with external data
22 const onnxPath = ` ${ modelBase } /onnx/model_q4.onnx ` ;
23 const dataPath = ` ${ modelBase } /onnx/model_q4.onnx_data ` ;
24 const session = await ort . InferenceSession . create ( onnxPath , {
25 executionProviders : [ "webgpu" ] ,
26 externalData : [ { path : "model_q4.onnx_data" , data : dataPath } ] ,
27 } ) ;
28
29 // Model config (from config.json)
30 const hiddenSize = 2048 ;
31 const numKVHeads = 8 ;
32 const headDim = 256 ;
33
34 // Initialize KV cache
35 function initCache ( ) {
36 const cache = { } ;
37 for ( const name of session . inputNames ) {
38 if ( name . startsWith ( "past_conv" ) ) {
39 cache [ name ] = new ort . Tensor ( "float32" , new Float32Array ( hiddenSize * 3 ) , [ 1 , hiddenSize , 3 ] ) ;
40 } else if ( name . startsWith ( "past_key_values" ) ) {
41 cache [ name ] = new ort . Tensor ( "float32" , new Float32Array ( 0 ) , [ 1 , numKVHeads , 0 , headDim ] ) ;
42 }
43 }
44 return cache ;
45 }
46
47 // Update cache from outputs
48 function updateCache ( cache , outputs ) {
49 for ( const [ name , tensor ] of Object . entries ( outputs ) ) {
50 if ( name . startsWith ( "present_conv" ) ) {
51 cache [ name . replace ( "present_conv" , "past_conv" ) ] = tensor ;
52 } else if ( name . startsWith ( "present." ) ) {
53 cache [ name . replace ( "present." , "past_key_values." ) ] = tensor ;
54 }
55 }
56 }
57
58 // Build prompt and tokenize
59 const messages = [ { role : "user" , content : "What is 25 * 37?" } ] ;
60 const prompt = tokenizer . apply_chat_template ( messages , { add_generation_prompt : true , tokenize : false } ) ;
61 const inputIds = tokenizer . encode ( prompt ) ;
62
63 // Generation loop
64 const cache = initCache ( ) ;
65 const eosTokenId = tokenizer . eos_token_id ;
66 const generatedTokens = [ ] ;
67 let curLen = inputIds . length ;
68 let ids = inputIds ;
69
70 for ( let step = 0 ; step < 512 ; step ++ ) {
71 const inputIdsTensor = new ort . Tensor ( "int64" , new BigInt64Array ( ids . map ( BigInt ) ) , [ 1 , ids . length ] ) ;
72 const attentionMask = new ort . Tensor ( "int64" , new BigInt64Array ( curLen ) . fill ( 1n ) , [ 1 , curLen ] ) ;
73
74 const outputs = await session . run ( { input_ids : inputIdsTensor , attention_mask : attentionMask , ... cache } ) ;
75
76 // Greedy decode: argmax of last token logits
77 const logits = outputs . logits ;
78 const vocabSize = logits . dims [ 2 ] ;
79 const lastLogits = logits . data . slice ( ( logits . dims [ 1 ] - 1 ) * vocabSize ) ;
80 const nextToken = lastLogits . indexOf ( Math . max ( ... lastLogits ) ) ;
81
82 generatedTokens . push ( nextToken ) ;
83 if ( nextToken === eosTokenId ) break ;
84
85 updateCache ( cache , outputs ) ;
86 ids = [ nextToken ] ;
87 curLen ++ ;
88 }
89
90 // Parse thinking and response
91 const fullResponse = tokenizer . decode ( generatedTokens , { skip_special_tokens : true } ) ;
92 const thinkMatch = fullResponse . match ( / <think>([\s\S]*?)<\/think> / ) ;
93 if ( thinkMatch ) {
94 const thinking = thinkMatch [ 1 ] . trim ( ) ;
95 const answer = fullResponse . slice ( thinkMatch . index + thinkMatch [ 0 ] . length ) . trim ( ) ;
96 console . log ( "Thinking:" , thinking ) ;
97 console . log ( "Answer:" , answer ) ;
98 } else {
99 console . log ( fullResponse ) ;
100 }
<think>
To calculate 25 * 37, I can break this down:
25 * 37 = 25 * (40 - 3) = 25 * 40 - 25 * 3 = 1000 - 75 = 925
</think>
The answer is 925.
This model is released under the
LFM 1.0 License .