Instructions to use Suarify/laya-pdpa-onnx-v3-32k with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Laya
How to use Suarify/laya-pdpa-onnx-v3-32k with Laya:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
laya-pdpa-onnx-v3-32k
Why not just prompt an LLM?
On accuracy, you probably should. A frontier model would likely beat 83% on this task zero-shot, without two dead classes. Cost is not an argument either -- inference is cheap now -- and latency favours the LLM, which answers all ten questions in one call where this needs ten forward passes.
There is one argument, and for this job it is decisive:
The transcript never leaves your infrastructure. Sending a call recording containing a MyKad number, a card number and an OTP to a third-party API is itself a transfer you would have to justify under the law you are trying to comply with. A tool for finding PDPA exposure should not create it.
Alongside that: the answer is deterministic and auditable -- same input, same output, forever, from a 192 MB file you hold -- where a hosted model can change beneath you between audits. And it runs with no network at all, including in a browser tab.
The sensible design is a cascade, not a replacement.
| layer | use it for | why |
|---|---|---|
| regex | email, OTP, IC, card, phone | free, deterministic, and it beats this model on pdpa_email (100%) and pdpa_credential_otp (94.1%) |
| this model | name, address, severity, actor; precision on the digit classes | decides whether a twelve-digit number is a card or an order id, which a pattern cannot |
| an LLM | whatever is left | only if your data policy permits sending the transcript |
Measured on the same 51 conversations, a page of regexes scores 100% on pdpa_email
against the model's 94.1%. Where the model wins on pdpa_mykad (96.1% vs 76.5%) it wins
on precision: the regex fires on order ids, tracking numbers and invoice amounts, which
is what the corpus's 16 hard-negative slots were built to teach.
Read the gain, not the headline
Accuracy is quoted against a majority-class baseline of 74.1% on 51 held-out conversations. So 83.1% is about nine points of signal, not eighty-three. Per question, gain over always answering with the most common label:
| question | majority | model | gain |
|---|---|---|---|
pdpa_name |
52.9% | 94.1% | +41.2 |
pdpa_severity |
41.2% | 62.7% | +21.5 |
pdpa_address |
82.4% | 100.0% | +17.6 |
pdpa_actor |
41.2% | 56.9% | +15.7 |
pdpa_phone |
82.4% | 94.1% | +11.7 |
pdpa_mykad |
88.2% | 96.1% | +7.9 |
pdpa_email |
88.2% | 94.1% | +5.9 |
pdpa_health |
88.2% | 92.2% | +4.0 |
pdpa_credential_otp |
88.2% | 88.2% | 0.0 β at chance |
pdpa_bank_card |
88.2% | 86.3% | β1.9 β below chance |
Two questions do not work
pdpa_bank_card and pdpa_credential_otp perform at or below a constant "no". Each had
six training conversations carrying the class. Do not gate on them. An earlier
release reported both at 100%, which was true and meaningless: that test split contained
no positive examples of either, so answering "no" unconditionally scored perfectly.
Three further limits
- It is sensitive to phrasing. The same call, same IC, same card, same OTP, with the
surrounding turns in English versus Malay:
pdpa_mykadanswers yes at 95% on one and no at 7% on the other. - Severity reverts to its training prior on unfamiliar text. On 113 real Malay conversations screened to contain no disclosures, it called 51% of them medium-or-worse while every binary class answered "no". Never surface severity without the class that justifies it.
- A regex beats this model on two classes. On the same test split a page of patterns
scores 100% on
pdpa_emailand 94.1% onpdpa_credential_otp. The model's advantage on the digit classes is precision against near-misses β deciding whether a twelve-digit number is a card or an order id β not detection.
All training data is synthetic; no real call has ever been labelled. Validate on your own traffic before trusting any of this.
ONNX export of Suarify/laya-pdpa-my-v3 β a Malaysian
PDPA classifier for call transcripts. Ten typed questions per call: eight yes/no
classes, one ordered severity, one choice for who disclosed.
Exported with torch.onnx.export(dynamo=True), opset 18, external data.
Variants
| file | size | accuracy | 1 question, 4 vCPU |
|---|---|---|---|
onnx/model.onnx |
602 MB | 85.3% | 2005 ms |
onnx/model_fp16.onnx |
β | does not load | β |
onnx/model_q4f16.onnx |
192 MB | 83.1% | 2097 ms |
Parity against the PyTorch checkpoint: 8.30e-05 max absolute logit difference across batches 1, 3, 8 and 16.
Pruned vocabulary
The base checkpoint carries a 256,000-token multilingual vocabulary, which made
the embedding table 61% of its weights. MatMulNBitsQuantizer skips Gather, so
quantisation never touched it and the full-vocab q4f16 build still weighed 880 MB.
This export keeps 32,000 tokens, chosen on the train and validation splits only, and slices the embedding to match: 256,000 -> 32,000 rows, 322M -> 150M parameters.
embedding fp32 98 MB fp16 49 MB
everything else fp32 501 MB 4-bit 70 MB
The keep-set is script-aware. Ids below 32,000 hold 28,564 Latin tokens but only 571 CJK and 27 Tamil, so keeping the lowest ids would leave Chinese and Tamil encoding as raw bytes. Every single-character CJK and Tamil token is kept, and byte fallback is retained, so no input is ever unencodable.
The tokenizer in this repo is not interchangeable with the base one. Token ids
differ. Use tokenizer.json from here or every answer is read from wrong rows.
Inputs
input_ids int64 [batch, seq]
attention_mask int64 [batch, seq]
marker_pos int64 [batch, options] index of each [MASK] slot
marker_mask bool [batch, options]
qtype int64 [batch] 0=choice 1=score 2=noul
-> logits float [batch, options]
This is not a standard sequence classifier and transformers.js cannot load it.
The sequence must be built as laya.common.build_sequence does:
[CLS] <type> question: <ins> [SEP] [MASK] opt0 [MASK] opt1 ... [SEP] state [SEP]
with marker_pos carrying each [MASK] index. Special tokens are mmBERT's
<bos>/<eos>/<mask>/<pad> = 2/1/4/0, not [CLS]/[SEP]. Option order is label
order β get it wrong and every answer is silently relabelled, not an error.
A working single-file browser port is in the web/ directory of the source repo.
Segmentation is unchanged for text the vocabulary was fitted on, and near-identical on held-out text; the export log for this run prints both figures.
Calibration
temperature (choice, score, noul) = [10.0, 10.0, 7.915902137756348]
choice and score sit exactly on the 10.0 clamp: the optimiser wanted to soften
further and could not, so those logits are saturated and their confidence is
unreliable. Accuracy is unaffected β temperature does not move argmax. noul
fitted interior to the range and is genuinely calibrated.
Limitations
All training data is synthetic; no real call has ever been labelled. Disclosure
prevalence in the corpus is far above production, so precision here is optimistic
by roughly an order of magnitude. pdpa_actor is at 50% and should not be gated
on. See the base model card for the full list.
- Downloads last month
- -
Model tree for Suarify/laya-pdpa-onnx-v3-32k
Base model
convaiinnovations/laya-multilingual