Instructions to use dizyann/crivo-web with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers.js
How to use dizyann/crivo-web with Transformers.js:
// ⚠️ Unknown pipeline tag
File size: 6,898 Bytes
6986878 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | """Reproducible full-graph Crivo export. Conversion-only Python; app runtime stays TypeScript.
Run with the pinned environment in requirements.txt. Source snapshots must match sources.json.
"""
import argparse, gc, importlib.util, json, shutil, sys
from pathlib import Path
import numpy as np
import onnx
import onnxruntime as ort
from quantize import quantize_weights
import torch
from transformers import AutoTokenizer, ModernBertConfig, ModernBertModel
from safetensors.torch import load_file
parser = argparse.ArgumentParser()
parser.add_argument('size', choices=['17m','32m','68m','150m','400m','1b'])
args = parser.parse_args()
root = Path(__file__).resolve().parents[2]
source = root / '.cache/conversion' / args.size
out = root / '.cache/variants' / args.size
out.mkdir(parents=True, exist_ok=True)
(out/'onnx').mkdir(exist_ok=True)
meta = json.loads((source/'source.json').read_text())
assert meta == json.loads(Path(__file__).with_name('sources.json').read_text())[args.size], 'Source revision mismatch'
torch.set_num_threads(4)
spec = importlib.util.spec_from_file_location('upstream_model', source/'model.py')
upstream = importlib.util.module_from_spec(spec)
spec.loader.exec_module(upstream)
original = json.loads((source/'config.json').read_text())
config = ModernBertConfig(**{k:v for k,v in original.items() if k not in ('architectures','decision')})
config._attn_implementation = 'eager'
config.reference_compile = False
model = upstream.DecisionModel(ModernBertModel(config), config.hidden_size).eval()
model.load_state_dict(load_file(str(source/'model.safetensors')), strict=True)
tok = AutoTokenizer.from_pretrained(source)
tok.save_pretrained(out)
tc = json.loads((out/'tokenizer_config.json').read_text())
tc['tokenizer_class'] = 'PreTrainedTokenizer'
(out/'tokenizer_config.json').write_text(json.dumps(tc,indent=2)+'\n')
exported = {**original, 'model_type':'custom','architectures':['PreTrainedModel'], 'source':meta}
# Browser release initially retains the tested 7,999-token cap, not upstream's 32k claim.
exported['web_max_length'] = 7999
names = ['input_ids','attention_mask','opt_positions','opt_mask','qtype']
encoder = upstream.Encoder(tok)
cases = [
('Please refund my duplicate payment.', [('choice','Identify the intent.',{'refund':'Return money','cancel':'Cancel order','other':'Another request'}),('noul','The customer was charged twice.',None),('score','Rate urgency.',['Low','Medium','High'])]),
('The order is fine, I only want to know the delivery date.', [('choice','Identify intent.',{'question':'Ask a question','refund':'Return money'} )]),
('A long support request. '*30,[('score','Rate urgency.',['Low','High']),('noul','The service is unavailable.',{'true':'Unavailable','false':'Available'})])
]
def inputs(state, questions):
rows=[]
for kind,instructions,criteria in questions:
options,_=upstream.option_texts(kind,criteria,instructions)
ids,pos=encoder.encode(instructions,state,options)
rows.append((ids,pos,upstream.QUESTION_TYPES[kind]))
return upstream.collate(rows,tok.pad_token_id)
samples=[inputs(*case) for case in cases]
references=[]
with torch.inference_mode():
for feeds in samples: references.append(model(**feeds).numpy())
graph=out/'onnx/model.onnx'
print('EXPORT',args.size,flush=True)
with torch.no_grad():
torch.onnx.export(model,tuple(samples[0][name] for name in names),str(graph),input_names=names,output_names=['logits'],opset_version=17,dynamo=False,external_data=True,dynamic_axes={**{n:{0:'batch',1:'sequence'} for n in names[:2]},**{n:{0:'batch',1:'options'} for n in names[2:4]},'qtype':{0:'batch'},'logits':{0:'batch',1:'options'}})
del model;gc.collect()
# Keep each external buffer below browser ArrayBuffer limits.
def shard_external(path):
g=onnx.load(str(path))
index=0;offset=0;handle=None
try:
for tensor in g.graph.initializer:
if not tensor.HasField('raw_data'): continue
data=tensor.raw_data
if handle is None or offset+len(data)>1024**3:
if handle: handle.close();index+=1
location=path.name+'_data'+('' if index==0 else '_'+str(index))
handle=(path.parent/location).open('wb');offset=0
onnx.external_data_helper.set_external_data(tensor,location,offset,len(data))
handle.write(data);offset+=len(data)
tensor.ClearField('raw_data');tensor.data_location=onnx.TensorProto.EXTERNAL
finally:
if handle: handle.close()
onnx.save_model(g,str(path))
del g;gc.collect()
return index+1
if args.size=='1b':
exported['transformers.js_config']={'use_external_data_format':{'model.onnx':shard_external(graph)}}
(out/'config.json').write_text(json.dumps(exported,indent=2)+'\n')
onnx.checker.check_model(str(graph))
opts=ort.SessionOptions();opts.intra_op_num_threads=4
session=ort.InferenceSession(str(graph),sess_options=opts,providers=['CPUExecutionProvider'])
fp_delta=0
for feeds,expected in zip(samples,references):
actual=session.run(['logits'],{n:feeds[n].numpy() for n in names})[0]
np.testing.assert_allclose(actual,expected,rtol=1e-3,atol=1e-4)
fp_delta=max(fp_delta,float(np.max(np.abs(actual-expected))))
del session;gc.collect()
print('QUANTIZE',args.size,flush=True)
quant=out/'onnx/model_quantized.onnx'
quantize_weights(graph, quant)
if args.size=='1b':
exported['transformers.js_config']['use_external_data_format']['model_quantized.onnx']=shard_external(quant)
(out/'config.json').write_text(json.dumps(exported,indent=2)+'\n')
onnx.checker.check_model(str(quant))
session=ort.InferenceSession(str(quant),sess_options=opts,providers=['CPUExecutionProvider'])
quant_delta=0
fixtures=[]
for (state,questions),feeds,expected in zip(cases,samples,references):
actual=session.run(['logits'],{n:feeds[n].numpy() for n in names})[0]
assert np.isfinite(actual).all()
p=torch.from_numpy(expected).double().softmax(-1).numpy()
q=torch.from_numpy(actual).double().softmax(-1).numpy()
quant_delta=max(quant_delta,float(np.max(np.abs(p-q))))
fixtures.append({'state':state,'questions':[{'type':k,'instructions':i,**({'criteria':c} if c is not None else {})} for k,i,c in questions],'fp32':p.tolist(),'int8':q.tolist()})
report={'quantization':{'storage':'int8','arithmetic':'fp32','scheme':'symmetric-per-channel-weight-only'},'source':meta,'fp32_max_logit_error':fp_delta,'int8_max_probability_error':quant_delta,'cases':fixtures,'versions':{'torch':torch.__version__,'onnx':onnx.__version__,'onnxruntime':ort.__version__}}
# Retain only the final graphs and referenced external buffers.
keep = {'model.onnx', 'model_quantized.onnx'}
for path in [graph, quant]:
g=onnx.load(str(path),load_external_data=False)
for tensor in g.graph.initializer:
keep.update(item.value for item in tensor.external_data if item.key=='location')
for path in (out/'onnx').iterdir():
if path.is_file() and path.name not in keep: path.unlink()
(out/'parity.json').write_text(json.dumps(report,indent=2)+'\n')
print('PASS',args.size,fp_delta,quant_delta,flush=True)
|