跪拜 Guibai
← Back to the summary

A 1.5M-Parameter OCR Model Now Runs Entirely in the Browser

A while back I saw that the PaddleOCR team open-sourced PP-OCRv6, with three tiers of models.

The smallest, the Tiny tier, has only 1.5M parameters! And they say it can run in the browser!

The key is that the performance isn't bad either, supporting 49 languages!

PP-OCRv6 three tiers vs. general multimodal large models score comparison

This Tiny model scored 80.6 on the official text detection benchmark, while the highest score among those general multimodal large models in the chart is only 46.8; its text recognition score is 73.5, just a little behind the best-performing Qwen3-VL-235B.

It's honestly pretty impressive.

To be honest, I was quite shocked...

What does 1.5M parameters mean?

In more familiar units, 1.5M is 0.0015B (1B = 1 billion, 1.5M = 1.5 million).

1.5M parameters vs. common model scale multiples

What does running OCR in the browser mean?

Images don't need to be uploaded to a server, no waiting in queues, no per-token billing, and data never leaves your device.

Driven by curiosity, I built a project to verify it, and it indeed runs in the browser.

I also deployed it online; interested friends can try it at ocr.laifuyou.com.

It runs purely in the browser, without going through any server.

Demo actual recognition result: detection boxes and recognized text

The image was one I casually uploaded; the boxes drawn on it are the recognition results.

Let's talk about a few numbers first

1.5M is the parameter count, not the file size.

The two model files for the Tiny tier, det.onnx + rec.onnx, plus the dictionary file, total about 6MB.

Additionally, the ONNX Runtime's WASM runtime needs to be downloaded on the first visit; afterwards, the browser caches it, so it doesn't need to be downloaded again on subsequent visits.

PP-OCRv6 has three tiers in total, scaled from the same architecture:

Tier Parameters Model Size Detection Accuracy Recognition Accuracy Languages
Tiny 1.5M ~6MB 80.6% 73.5% 49 (excluding Japanese)
Small 7.7M ~30MB 84.1% 81.3% 50
Medium 34.5M ~132MB 86.2% 83.2% 50

Size difference between the three tiers of models

To push parameter compression to the extreme, Tiny's recognition encoder was cut significantly, with accuracy recovered through distillation.

The Japanese vocabulary is too large, so it's not supported.

Although recognition accuracy is about 10% lower than Medium, it's sufficient for everyday screenshots and document photos.

If higher recognition requirements exist, you can switch to the Small model.

The size footprint is acceptable, and the recognition quality is also significantly improved.

OCR and large models are not the same thing

Previously, I thought OCR and large models were the same thing.

After some research, I found that they are not.

For OCR, the answer already exists within the image; the model just "reads it out."

For large models like LLMs, the answer does not exist within the input text.

To summarize simply:

OCR is more of a "recognition problem," while LLMs are more of a "generation problem."

OCR is sensitive to "pixels," while LLMs are sensitive to "semantics."

Specifically for OCR itself, it mainly involves two steps:

Detection: Find where the text is, outputting a set of boxes.

Recognition: Crop out each box and recognize what characters are inside.

OCR two-step process: Original image → Detection → Cropping → Recognition

Correspondingly, the code also follows these two steps:

// Step 1: Detection — feed the image into DBNet, output a probability map, post-process to find text boxes
const detResult = await detSession.run({ x: imageTensor });
// Probability map → binarization → connected components → text boxes
const boxes = findTextBoxes(detResult);

// Step 2: Recognition — crop each box, feed them one by one into the recognition model
for (const box of boxes) {
  // Crop box, resize to fixed height
  const cropped = cropAndResize(image, box);
  const recResult = await recSession.run({ x: cropped });
  // CTC decoding: probability matrix → text
  const text = ctcDecode(recResult);
}

The two models each have their own job: det handles "where the text is," rec handles "what the text is." Together, they total 1.5M parameters.

Why OCR models can be so small

I was curious why OCR models can be this small.

It can actually be understood this way.

An LLM is a general-purpose assistant that has to learn everything.

Such as: programming, math, history, law, medicine, writing, English, Chinese, reasoning, world knowledge...

OCR, on the other hand, is a specialist; it only does one thing, as long as it does that one thing well.

Such as: finding the position of text in an image, recognizing the characters one by one.

The task boundary is much narrower.

General assistant vs. specialist capability range comparison

That's why a specialized model like PP-OCRv6 with 1.5M parameters can compete with general models of tens of billions of parameters on OCR-specific metrics, even leaving them far behind in text detection.

This is actually similar to humans.

A professional barcode scanner can recognize a barcode faster than a robot.

It's not that the robot isn't advanced, but that the specialized system dedicates its entire capacity to a single problem.

Think about it: many tasks don't need brute force; not everything requires a large model.

How the model runs in the browser

Previously, OCR required sending images to a server, running the model on the backend, and returning the results.

Now, the model runs directly in the browser. But browsers used to only run JavaScript; any slightly heavy computation had to be offloaded to a server. How is this achieved?

It relies on a key change in recent years: WebAssembly (WASM).

Simply put, WASM is a binary instruction format that browsers can execute directly. Programs written in C/C++/Rust, when compiled into WASM, can run in the browser at near-native speed.

You might already be using products powered by WASM — Figma's canvas engine runs as C++ compiled to WASM, and FFmpeg also has a browser version, allowing video transcoding to be done locally.

Three types of heavy computation running in the browser: design tools, video transcoding, text recognition

With WASM, mature libraries from the C/C++ ecosystem can be brought into the browser — including AI inference engines.

The core of AI model inference is a large number of matrix operations, which is traditionally a task GPUs excel at. Fortunately, modern browsers not only have WASM but also WebGPU and WebGL, which can access the GPU. Inference engines encapsulate these capabilities uniformly, giving the browser the complete conditions to run models.

With this foundation, the chain for running an OCR model in the browser becomes clear.

On-device inference chain: Model → ONNX → Inference Engine → Execution Backend → Local Result

Specifically, two tools are used: Paddle2ONNX exports the model trained by PaddleOCR into ONNX, a common interchange format, which is then handed over to Microsoft's onnxruntime-web to run in the browser.

A detail worth mentioning here: onnxruntime-web provides a unified inference layer, and the underlying execution can use different backends: WebGPU and WebGL can leverage GPU acceleration, while WASM runs on the CPU. During actual execution, it tries them one by one, using the fastest available backend.

WASM has the broadest compatibility, supported by almost all modern browsers, and is sufficient for small models. WebGPU can access the graphics card, but its support is narrower, and it might not be faster for small models — the time spent transferring data between CPU and GPU also matters.

Execution backend fallback: WebGPU → WebGL → WASM

In code, loading an ONNX model in the browser looks like this:

import * as ort from "onnxruntime-web";

// Tell the runtime where to find the WASM files
ort.env.wasm.wasmPaths = "/ort/";

// Try available execution backends one by one in priority order
const candidates = ["webgpu", "webgl", "wasm"];
for (const ep of candidates) {
  try {
    const session = await ort.InferenceSession.create(modelBuffer, {
      executionProviders: [ep],
    });
    // Success, use this backend
    break;
  } catch {
    // Current backend unavailable, try the next one
  }
}

No Python, no need to install GPU drivers; one npm package brings the inference engine into the browser.

Finally

After finishing this little project, a strong feeling I have is: not every task needs a large model.

For different scenarios and tasks, a suitable small model can also play a significant role.

Previously, the browser was a container for running interfaces, with computation and storage on the server side.

Now, this division of labor is changing. WASM provides an entry point for general computation, WebGPU can access the graphics card, encoding/decoding and local files each have corresponding interfaces, and when you add AI inference, the browser increasingly resembles a lightweight local runtime.

The changing role of the browser: from running interfaces to a lightweight local runtime

Specialized models are small enough to fit into the browser; once the browser can run inference, specialized small models find a new landing spot.

There's a widely circulated saying in programmer circles — Atwood's Law: Anything that can be written in JavaScript, will eventually be written in JavaScript.

I used to treat it as a joke. JS can write frontends, backends, clients, and now it can even run AI models.

Tell me loudly: What is the best language in the world? 🤓

Project Links