Instructions to use lightonai/LightOnOCR-2-1B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use lightonai/LightOnOCR-2-1B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="lightonai/LightOnOCR-2-1B") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoProcessor, AutoModelForSeq2SeqLM processor = AutoProcessor.from_pretrained("lightonai/LightOnOCR-2-1B") model = AutoModelForSeq2SeqLM.from_pretrained("lightonai/LightOnOCR-2-1B", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use lightonai/LightOnOCR-2-1B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "lightonai/LightOnOCR-2-1B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "lightonai/LightOnOCR-2-1B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/lightonai/LightOnOCR-2-1B
- SGLang
How to use lightonai/LightOnOCR-2-1B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "lightonai/LightOnOCR-2-1B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "lightonai/LightOnOCR-2-1B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "lightonai/LightOnOCR-2-1B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "lightonai/LightOnOCR-2-1B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use lightonai/LightOnOCR-2-1B with Docker Model Runner:
docker model run hf.co/lightonai/LightOnOCR-2-1B
I just get only a small part of the table's content: how to make it work?
Hi @staghado
I strangely do not get all the rows of a table
This is how I start LightOnOCR-2-1B :
./llama-server --cors-origins localhost -m LightOnOCR-2-1B.f16.gguf --mmproj LightOnOCR-2-1B-mmproj-f16.gguf
And this is the code I use to grab the OCR output:
bool TextExtractionFromImg::getContent()
{
try
{
std::string imagefilepath_s(imagefilepath.begin(), imagefilepath.end());
const auto image = read_binary_file(imagefilepath_s);
const std::string encoded_image = base64_encode(image);
const std::string mime_type = get_mime_type(imagefilepath_s);
// "Output only the recognized text in Markdown.";
const std::string prompt =
"OCR the document. Preserve the natural reading order, "
"tables, line breaks, rows, and mathematical notation. "
"output all the content of all rows till the end of the table";
std::ostringstream json;
json << R"({
"messages": [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "data:)"
<< mime_type
<< ";base64,"
<< encoded_image
<< R"("
}
},
{
"type": "text",
"text": ")";
// This prompt contains no JSON-special characters.
json << prompt;
json << R"("
}
]
}
],
"temperature": 0.2,
"max_tokens": 1000
})";
const std::string request_body = json.str();
CURL* curl = curl_easy_init();
if (!curl)
{
throw std::runtime_error("Could not initialize libcurl");
return false;
}
std::string response;
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(
curl,
CURLOPT_URL,
"http://127.0.0.1:8080/v1/chat/completions"
);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(
curl,
CURLOPT_POSTFIELDS,
request_body.c_str()
);
curl_easy_setopt(
curl,
CURLOPT_POSTFIELDSIZE,
static_cast<long>(request_body.size())
);
curl_easy_setopt(
curl,
CURLOPT_WRITEFUNCTION,
write_callback
);
curl_easy_setopt(
curl,
CURLOPT_WRITEDATA,
&response
);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 600L);
const CURLcode result = curl_easy_perform(curl);
if (result != CURLE_OK)
{
std::cerr << "Request failed: " << curl_easy_strerror(result) << '\n';
else
{
long status_code = 0;
curl_easy_getinfo(
curl,
CURLINFO_RESPONSE_CODE,
&status_code
);
if (status_code >= 200 && status_code < 300)
{
try
{
nlohmann::json obj = nlohmann::json::parse(response);
std::string html_s = obj["choices"][0]["message"]["content"];
std::cout << "html_s:" << std::endl;
std::cout << html_s << std::endl;
html_c = html_s.c_str();
std::cout << std::endl;
//getContentInMd();
}
catch (const nlohmann::json::parse_error& e)
{
std::cerr << "Invalid JSON: " << e.what() << '\n';
}
}
else
{
std::cerr << "HTTP " << status_code << ":\n" << response << '\n';
}
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return result == CURLE_OK ? true : false;
}
catch (const std::exception& error)
{
std::cerr << error.what() << '\n';
return false;
}
}
}
And this is the output:
html_s:
JUL 2026
# WORLD ECONOMIC OUTLOOK UPDATE: ANNEX
## Annex Table 1. Selected Economies: Real GDP Growth
*(Percent change)*
<table>
<thead>
<tr>
<th rowspan="2">Country</th>
<th rowspan="2">2024</th>
<th rowspan="2">2025</th>
<th colspan="2">Projections</th>
<th colspan="2">Difference from April 2026<br>WEO Projections 1/</th>
</tr>
<tr>
<th>2026</th>
<th>2027</th>
<th>2026</th>
<th>2027</th>
</tr>
</thead>
<tbody>
<tr><td>Argentina</td><td>–1.3</td><td>4.4</td><td>3.5</td><td>4.0</td><td>0.0</td><td>0.0</td></tr>
<tr><td>Australia</td><td>1.0</td><td>2.0</td><td>1.9</td><td>1.7</td><td>–0.1</td><td>0.0</td></tr>
<tr><td>Brazil</td><td>3.4</td><td>2.3</td><td>2.4</td><td>2.2</td><td>0.5</td><td>0.2</td></tr>
<tr><td>Canada</td><td>2.0</td><td>1.9</td><td>1.1</td><td>1.7</td><td>–0.4</td><td>–0.2</td></tr>
<tr><td>China</td><td>5.0</td><td>5.0</td><td>4.6</td><td>4.1</td><td>0.2</td><td>0.1</td></tr>
<tr><td>Egypt 2/</td><td>2.4</td><td>4.4</td><td>4.6</td><td>4.4</td><td>0.4</td><td>–0.4</td></tr>
<tr><td>France</td><td>1.4</td><td>0.9</td><td>0.6</td><td>0.9</td><td>–0.3</td><td>0.0</td></tr>
<tr><td>Germany</td><td>–0.5</td><td>0.2</td><td>0.7</td><td>1.0</td><td>–0.1</td><td>–0.2</td></tr>
<tr><td>India 2/</td><td>7.1</td><td>7.7</td><td>6.4</td><td>6.7</td><td>–0.1</td><td>0.2</td></tr>
<tr><td>Indonesia</td><td>5.0</td><td>5.1</td><td>5.0</td><td>5.1</td><td>0.0</td><td>0.0</td></tr>
<tr><td>Iran 2/ 3/</td><td>3.7</td><td>–0.8</td><td>–5.4</td><td>2.9</td><td>0.7</td><td>–0.3</td></tr>
<tr><td>Italy</td><td>0.8</td><td>0.5</td><td>0.5</td><td>0.5</td><td>0.0</td><td>0.0</td></tr>
<tr><td>Japan</td><td>–0.2</td><td>1.1</td><td>0.6</td><td>0.7</td><td>–0.1</td><td>0.1</td></tr>
As you can see, the output is partial: it contains only a small part of the actual table data
Modifying the prompt in this way:
const std::string prompt =
"Transcribe the entire table visible in the image."
"Requirements:"
"- Include every row and every column."
"- Preserve the original reading order."
"- Do not omit headers, footnotes, totals, blank cells, or partially visible cells."
"- Do not summarize or explain the table."
"- Return only one complete HTML <table>."
"- Use <thead> for the header when a header exists."
"- Use <tbody> for data rows."
"- Use colspan or rowspan when the image clearly contains merged cells."
"- If a cell is unreadable, write [illegible] rather than inventing text.";
I, still, get the same very partial result:
# WORLD ECONOMIC OUTLOOK UPDATE: ANNEX
## JUL 2026
### Annex Table 1. Selected Economies: Real GDP Growth
#### (Percent change)
<table>
<thead>
<tr>
<th rowspan="2">Country</th>
<th rowspan="2">2024</th>
<th rowspan="2">2025</th>
<th colspan="2">Projections</th>
<th colspan="2">Difference from April 2026<br>WEO Projections 1/</th>
</tr>
<tr>
<th>2026</th>
<th>2027</th>
<th>2026</th>
<th>2027</th>
</tr>
</thead>
<tbody>
<tr><td>Argentina</td><td>-1.3</td><td>4.4</td><td>3.5</td><td>4.0</td><td>0.0</td><td>0.0</td></tr>
<tr><td>Australia</td><td>1.0</td><td>2.0</td><td>1.9</td><td>1.7</td><td>-0.1</td><td>0.0</td></tr>
<tr><td>Brazil</td><td>3.4</td><td>2.3</td><td>2.4</td><td>2.2</td><td>0.5</td><td>0.2</td></tr>
<tr><td>Canada</td><td>2.0</td><td>1.9</td><td>1.1</td><td>1.7</td><td>-0.4</td><td>-0.2</td></tr>
<tr><td>China</td><td>5.0</td><td>5.0</td><td>4.6</td><td>4.1</td><td>0.2</td><td>0.1</td></tr>
<tr><td>Egypt 2/</td><td>2.4</td><td>4.4</td><td>4.6</td><td>4.4</td><td>0.4</td><td>-0.4</td></tr>
<tr><td>France</td><td>1.4</td><td>0.9</td><td>0.6</td><td>0.9</td><td>-0.3</td><td>0.0</td></tr>
<tr><td>Germany</td><td>-0.5</td><td>0.2</td><td>0.7</td><td>1.0</td><td>-0.1</td><td>-0.2</td></tr>
<tr><td>India 2/</td><td>7.1</td><td>7.7</td><td>6.4</td><td>6.7</td><td>-0.1</td><td>0.2</td></tr>
<tr><td>Indonesia</td><td>5.0</td><td>5.1</td><td>5.0</td><td>5.1</td><td>0.0</td><td>0.0</td></tr>
<tr><td>Iran 2/ 3/</td><td>3.7</td><td>-0.8</td><td>-5.4</td><td>2.9</td><td>0.7</td><td>-0.3</td></tr>
<tr><td>Italy</td><td>0.8</td><td>0.5</td><td>0.5</td><td>0.5</td><td>0.0</td><td>0.0</td></tr>
<tr><td>Japan</td><td>-0.2</td><td>1.1</td><td>0.6</td><td>0.7</td><td>-0.1</td><td>0.1</td></tr>
<tr><td>Kazakhstan</td><td>5.0
What am I doing wrong and/or missing?
How to get the whole table's content?
Hi, I think the issue is that you are asking for only 1000 tokens while for such tables you need 4k+.
One more thing, this model requires NO prompt and adding one hurts performance.
you still have ")" as the prompt and try asking for 4096 tokens so you actually can inspect the output before it times out.
I removed the prompt, and set an higher max_tokens .
I get : "Timeout was reached" :
bool TextExtractionFromImg::getContent()
{
try
{
std::string imagefilepath_s(imagefilepath.begin(), imagefilepath.end());
const auto image = read_binary_file(imagefilepath_s);
const std::string encoded_image = base64_encode(image);
const std::string mime_type = get_mime_type(imagefilepath_s);
std::ostringstream json;
json << R"({
"messages": [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "data:)"
<< mime_type
<< ";base64,"
<< encoded_image
<< R"("
}
},
{
"type": "text",
"text": ")";
// This prompt contains no JSON-special characters.
//json << prompt;
json << R"("
}
]
}
],
"temperature": 0.2,
"max_tokens": 5000
})";
const std::string request_body = json.str();
CURL* curl = curl_easy_init();
if (!curl)
{
throw std::runtime_error("Could not initialize libcurl");
return false;
}
std::string response;
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(
curl,
CURLOPT_URL,
"http://127.0.0.1:8080/v1/chat/completions"
);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(
curl,
CURLOPT_POSTFIELDS,
request_body.c_str()
);
curl_easy_setopt(
curl,
CURLOPT_POSTFIELDSIZE,
static_cast<long>(request_body.size())
);
curl_easy_setopt(
curl,
CURLOPT_WRITEFUNCTION,
write_callback
);
curl_easy_setopt(
curl,
CURLOPT_WRITEDATA,
&response
);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 600L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 600L);
const CURLcode result = curl_easy_perform(curl);
if (result != CURLE_OK)
{
std::cerr << "Request failed: " << curl_easy_strerror(result) << '\n';
}
else
{
long status_code = 0;
curl_easy_getinfo(
curl,
CURLINFO_RESPONSE_CODE,
&status_code
);
if (status_code >= 200 && status_code < 300)
{
try
{
nlohmann::json obj = nlohmann::json::parse(response);
std::string html_s = obj["choices"][0]["message"]["content"];
std::cout << "html_s:" << std::endl;
std::cout << html_s << std::endl;
html_c = html_s.c_str();
std::cout << std::endl;
//getMd(html_s);
//getContentInMd();
}
catch (const nlohmann::json::parse_error& e)
{
std::cerr << "Invalid JSON: " << e.what() << '\n';
}
}
else
{
std::cerr << "HTTP " << status_code << ":\n" << response << '\n';
}
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return result == CURLE_OK ? true : false;
}
catch (const std::exception& error)
{
std::cerr << error.what() << '\n';
return false;
}
}
0.00.006.028 I cmn common_param: common_params_print_info: verbosity = 3 (adjust with the `-lv N` CLI arg)
0.00.010.249 I srv load_model: loading model 'LightOnOCR-2-1B.f16.gguf'
0.00.490.176 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden
0.05.512.988 I srv load_model: loaded multimodal model, 'LightOnOCR-2-1B-mmproj-f16.gguf'
0.05.739.029 I srv load_model: initializing, n_slots = 4, n_ctx_slot = 16384, kv_unified = 'true'
0.05.748.121 I srv llama_server: model loaded
0.05.748.136 I srv llama_server: listening on http://127.0.0.1:8080
raphy@raphy:~/LightOnOCR-2-1B-Test$ ./builddir/LightOnOCR-2-1B-Test
Request failed: Timeout was reached
Interesting...
If I set max_token to 2000, I get the whole table's data:

