Introduction; Classifying at scale
Darknet monitoring aims to identify information relevant to specific operational topics. Accurate channel classification reduces the volume analysts must review and makes subsequent monitoring more focused. It also imporve accuracy of automatic detection.
However, when dealing with forums, Telegram channels, or other messaging platforms, several problems appear quickly.
First, there is the volume of data. For darknet forums, there are already dozens of forums to classify; but for Telegram channels, the number can quickly reach hundreds of thousands.
A second issue is language. How can a analysts classify forums written in Polish, Farsi, Chinese, or other languages when their native language is French ? Additionnaly, on some channels, multiple languages are used between several actors.
Even when classification is performed in a familiar language, slang remains difficult. In some communities, users deliberately employ specialized vocabulary that is not present in standard language.
An example of such specialized vocabulary:
Need MQR
SBI CMP + MQR(High%) 🔥
SBI 3ID/4ID/5ID/6ID+MQR (High%) 🔥
Razorpay X + RBL + MQR (High%) 🔥
Equitasbank + MQR(High%) 🔥
RBL + MQR(High%) 🔥
KVB + MQR(High%) 🔥
Federal One + MQR 🔥
BOM + MQR 🔥
TMB + MQR 🔥
IDBI + MQR 🔥
DBS + MQR 🔥
If your bank isn't up there, you can send us a test.
ALL OF ABOVE ACC NEED URGENTLY
High Percentage
Highest Volume in the Market
Run Fast and Efficient
Stable Daily
Need a lot of SBI 3 (admin, maker, checker), daily volume 3-5cr. High percentage. Welcome to send brothers. Support!! 🔥
In this simple example, a user request for a State Bank of India (SBI) corporate account configured with 3 user roles:
- Admin: manages users, permissions, and settings.
- Maker: creates/initiates payments.
- Checker: reviews and approves payments before execution. Mentionning that he need, Merchant QR code (MQR) for transactions like UPI/Google Pay/PhonePe.
Prior work has demonstrated that automated Telegram classification is feasible. Roy et al.’s DarkGram analyzed 339 cybercriminal activity channels and 53,605 posts. Its BERT-based text classifier reported an average accuracy of 96% across five predefined categories. This result supports the use of a constrained label space rather than freely generated labels.
DarkGram nevertheless addresses a narrower setting: its seed channels were English-language channels with at least 10,000 followers, classification operated at post level, and the output space contained five broad categories.
By contrast, our corpus consists of randomly selected channels without a language constraint. It therefore includes multilingual and mixed-language content, community slang, and heterogeneous topics. We classify channel-level samples using a finer cybersecurity taxonomy. DarkGram’s reported accuracy is consequently not directly comparable with our metrics. Our work builds on the same general objective using open-weight LLMs, a more granular taxonomy, and cross-model validation.
Since Large Language Models has been trained on several corpus, our idea was to investigate LLMs capability to classify Telegram channels from a sufficient sample of their collected text. The remaining question was how to supervise the quality of the classification output. Since several LLMs were evaluated, we used their validation responses to derive a consensus between models.
Hardware used
Experiments ran on an internal server with 224 Intel Xeon Platinum 8480+ CPU cores, approximately 2 TB RAM, and two NVIDIA GH100 H100L GPUs with 94 GB memory each. Ollama provided a common serving interface, allowing models to be switched without changing the evaluation pipeline.
Model architectures, parameter counts, quantization formats, and resource requirements differed substantially. Most models could be served on one H100, whereas some big models like devstral-2:latest required memory from both GPUs. Latency therefore reflects both model characteristics and resource allocation; it is not a hardware-only benchmark.
Dataset
The dataset was built from Telegram channels collected continuously over several years using tools from our open-source AIL Framework. AIL provides collection, ingestion, indexing, and analysis capabilities for unstructured intelligence data, including Telegram and other chat sources.
For this experiment, we drew a random sample representing approximately 3% of the complete dataset available in June 2026. After preparation, the evaluation corpus contained 1,228 channels and 116,083 posts. No language filter was applied: the sample is multilingual and includes both single-language and mixed-language channels, as well as slang, transliterated text, and heterogeneous subject matter.
Only textual content was evaluated. The channel name and description, message author names, message text, URLs, and textual references to attachments were retained when available, but images, audio, video, and attached-file contents were not analyzed. This scope isolates text-classification performance and avoids introducing model-dependent multimedia capabilities into the comparison.
Initial work and iterative evaluation
The evaluation protocol was developed iteratively. Each run addressed limitations observed in the previous one: uncontrolled labels in the first run, taxonomy and output-format limitations in the second, then robustness and scale in the final run.
Assessing classification quality at scale required a reference against which model outputs could be compared. Because a manually annotated ground-truth dataset was not available, we designed a multi-model cross-validation protocol. Each model first proposed tags independently. The union of all proposed tags was then submitted to the participating models, which were asked to validate each tag against the original channel sample.
Validation decisions were aggregated into a consensus for each tag. This consensus served as an operational reference for measuring first-pass precision, consensus recall, overtagging, and disagreement between models. The approach does not replace human ground truth, but it provides a reproducible way to compare models while reducing dependence on any single classifier.
Run 1: open-ended tagging
Models were initially asked to invent tags freely from channel content. This open-label approach proved unsuitable. Different models used synonyms, different levels of abstraction, and different wording for equivalent content. The result was a fragmented label space that could not be aggregated or compared reliably across channels. This also limited the models’ ability to converge during validation.
- account_rentals
- account_sales
- account_sellings
- accountabilities
- accountants
- accountings
- accountmanagements
- accounts
- accountsellings
- accountsharing
Example of diversity of a tag “account”
example of diversity of tags cross-models and family for a given telegram channel
Output handling created a second problem. Natural-language responses were not consistently machine-processable: models added explanations, Markdown, malformed values, or unexpected formatting. Although JSON output would normally reduce ambiguity, not every model supported reliable JSON responses through Ollama. Some returned invalid JSON or empty responses when structured output was requested.
# mistral-nemo:12b-instruct-2407-fp16
raw output:
Based on the provided channel messages, here are the relevant tags from the allowed list:
- dark-web:motivation="marketplace-for-sale"
- dark-web:topic="finance"
- dark-web:topic="hacking"
Random output of a mistral model reqired to response a JSON object
The first run evaluated 28 candidate models able to run on ollama, the run was done on an initial 20 channel load. Models with fewer reports produced incomplete or unusable outputs during this preliminary stage.
| Model | Reports evaluated |
|---|---|
qwen3.5:0.8b |
20 |
qwen2.5vl:latest |
20 |
ollama.com/library/llama3.1:latest |
20 |
ollama.com/library/llama3.1:70b |
20 |
mistral-small3.2:24b-instruct-2506-fp16 |
20 |
llama4:latest |
20 |
deepseek-r1:32b |
20 |
qwen3.5:2b |
12 |
qwen3.5:4b |
4 |
mistral-nemo:12b-instruct-2407-fp16 |
20 |
qwen3.5:9b |
13 |
devstral-2:latest |
20 |
qwen3.5:35b |
17 |
qwen3-coder:latest |
20 |
qwen3.5:122b |
20 |
nemotron3:33b |
20 |
granite4.1:30b |
20 |
qwen3-coder-next:q4_K_M |
20 |
deepseek-r1:8b |
20 |
qwen3-coder-next:q8_0 |
20 |
gpt-oss:120b |
20 |
qwen3.6:35b |
16 |
qwen3.6:35b-a3b |
17 |
qwen3.6:35b-a3b-bf16 |
19 |
nemotron-3-super:latest |
20 |
igorls/gemma4-e4b-classifier:q8_0 |
20 |
mistral-medium-3.5:latest |
20 |
gemma4:31b |
20 |
This run showed that useful comparison required both a shared vocabulary and strict output validation.
This first run also highlighted the value of requiring justifications. Evidence-based explanations make weak or unsupported classifications easier to identify and provide a basis for systematic validation. This led to the central research question: which models produce the most relevant, evidence-supported tags while avoiding overtagging and spurious labels?
Run 2: taxonomy-constrained tagging
The second run replaced free tags with a predefined taxonomy containing categories and subcategories. The first taxonomy evaluated was the existing MISP dark-web taxonomy. It was tested on 50 channels. Each model first classified a channel, then every proposed tag was challenged by the validation models. This made it possible to compare first-pass tagging quality, identify overtagging, and derive cross-model agreement.
A controlled taxonomy did not eliminate output failures. Recurring issues included:
- invalid or almost-valid taxonomy UUIDs;
- taxonomy identifiers returned instead of requested tag values;
- missing quotation marks or malformed machine-readable values;
- additional explanations around expected output;
- invalid JSON or empty responses;
- timeouts, incomplete responses, and ignored instructions.
For example, devstral-2:latest returned UUIDs and introduced a one-character error in the UUID associated with anti-entity:
# devstral-2:latest
elapsed_second_request: 25.00s
extremist,forensics,nationalist,pro-entity,threat-intelligence
raw output:
0ce3bbf3-b2b8-5508-a53e-9a9ad8c6ef42
15230a5d-8364-56ae-9d92-7c340e4097ab
255c1e12-cc19-50c0-920d-e478dad2d90a
ac3d1958-666a-5727-82d8-e11f3b05446b
6c577620-d930-5a54-956d-2f191bf4abae
e39587b0-1b4c-5279-b085-543d91822ffb
The valid taxonomy UUID ends in 46a, not 46b. This example shows that syntactically plausible output can still be semantically invalid.
The pipeline was therefore improved with explicit output constraints, extractable response formats, exact taxonomy matching, limited UUID correction, parsing checks, and a second validation step. Failures remained part of the measured results. Models unable to produce sufficiently stable outputs were excluded from the large-scale run.
Run 3: large-scale evaluation
Constraining the label space improved comparability, but the existing dark-web taxonomy could not adequately describe the diversity of the sampled channels. It focused primarily on underground activities and lacked sufficient coverage for legitimate communities, general-interest content, technology, finance, geopolitics, media, and mixed-purpose channels. Continuing with it would have forced models either to omit relevant themes or to select labels that only partially matched the content.
We therefore created the broader MISP content-classification taxonomy. It organizes 75 controlled values under 11 thematic predicates: general content, technology, cybersecurity, cybercrime, markets, communities, digital content, finance, illegal markets, technical services, and geopolitics and hacktivism. Each value has a stable UUID, a human-readable label, and a definition supplied to the models in the prompt. This structure preserves machine-readable output while providing enough breadth and granularity to classify both malicious and non-malicious channels.
The final run applied the improved protocol and this content-classification taxonomy to 1,228 channel reports. Thirteen models that were sufficiently robust during preliminary testing were retained:
| Model |
|---|
gemma4:31b |
qwen3.5:122b |
gemma4:e4b |
igorls/gemma4-e4b-classifier:q8_0 |
qwen3.6:35b |
gemma4:12b |
nemotron-3-super:latest |
mistral-medium-3.5:latest |
gpt-oss:120b |
deepseek-r1:32b |
devstral-2:latest |
mistral-small3.2:24b-instruct-2506-fp16 |
granite4.1:30b |
This progression transformed an exploratory tagging experiment into a reproducible model evaluation: Run 1 exposed label variability, Run 2 introduced taxonomy-based tagging and validation, and Run 3 measured quality, robustness, and latency at scale.
Final setup
Channel samples were prepared to contain approximately 10,000 tokens, with a target variation of ±10%. This normalization limits input-length effects when comparing models. Token counts remain tokenizer-dependent: different models may segment identical text differently.
Preprocessing used the o200k_base encoding from tiktoken, with a hard input limit of 10,000 tokens according to that encoding. This provides a reproducible reference count, not an exact native-token count for every model.
The evaluation used a multi-stage protocol:
- each model proposed taxonomy tags from a channel sample;
- proposed tags were submitted to validation models;
- validation responses determined whether each tag was supported;
- accepted tags were aggregated into an operational consensus;
- initial predictions were compared with this consensus.
Failed reports were retained in quality metrics and assigned a score of zero. Missing model outputs were ignored only for the corresponding metric denominator.
Five complementary dimensions were measured:
- failure rate, combining initial tagging and correction/validation failures;
- contradiction rate, measuring proposed tags rejected during validation;
- consensus recall, measuring consensus tags found during initial tagging;
- tagging precision, measuring proposed tags confirmed as valid;
- F1 score, balancing precision and consensus recall.
Initial tagging and validation latency were also measured. In order to avoid bias in time measurement, the load of the model was not taken in consideration. For that we loaded the model ask a dummy question then we start the clock only after this initial dummy query.
Results
Reports aggregated from the classification of 1,228 telegram channel. The corresponding channel samples contain 116,083 posts. Missing models are simply ignored when calculating the metric.
Most failing model
This score measures which models most often failed to answer in time or returned invalid tagging/correction output. It counts both initial tagging errors. The score is (initial tagging errors + correction errors) / (initial tagging attempts + correction attempts) * 100. Higher is worse: models at the top failed most often.
| model | reports | initial tagging errors | correction errors | total errors | attempts | score |
|---|---|---|---|---|---|---|
| gemma4:12b | 1228 | 13/1228 | 3/1211 | 16 | 2439 | 0.66% |
| deepseek-r1:32b | 1228 | 7/1228 | 1/1037 | 8 | 2265 | 0.35% |
| devstral-2:latest | 1228 | 5/1228 | 0/1038 | 5 | 2266 | 0.22% |
| qwen3.6:35b | 1228 | 4/1228 | 0/1142 | 4 | 2370 | 0.17% |
| nemotron-3-super:latest | 1228 | 4/1228 | 0/1087 | 4 | 2315 | 0.17% |
| granite4.1:30b | 1228 | 1/1228 | 2/1152 | 3 | 2380 | 0.13% |
| qwen3.5:122b | 1228 | 3/1228 | 0/1214 | 3 | 2442 | 0.12% |
| gemma4:31b | 1228 | 2/1228 | 0/1222 | 2 | 2450 | 0.08% |
| mistral-medium-3.5:latest | 1228 | 1/1228 | 0/1177 | 1 | 2405 | 0.04% |
| igorls/gemma4-e4b-classifier:q8_0 | 1228 | 1/1228 | 0/1225 | 1 | 2453 | 0.04% |
| mistral-small3.2:24b-instruct-2506-fp16 | 1228 | 0/1228 | 0/1209 | 0 | 2437 | 0.0% |
| gpt-oss:120b | 1228 | 0/1228 | 0/1198 | 0 | 2426 | 0.0% |
| gemma4:e4b | 1228 | 0/1228 | 0/1226 | 0 | 2454 | 0.0% |
Overall, all retained models were highly robust, with failure rates below 1%.
Most contradictory model
Cross-report contradiction rate. For each model, this averages the percentage of proposed tags later rejected during validation. Models that failed or did not produce usable tags are listed with a 100% score for that report and counted in error reports. Lower is better: the best score tends toward 0%.
| model | reports | error reports | sum | average | median | min | max |
|---|---|---|---|---|---|---|---|
| granite4.1:30b | 1228 | 78 | 69285.34% | 56.42% | 60.0% | 0.0% | 100.0% |
| devstral-2:latest | 1228 | 190 | 62089.82% | 50.56% | 50.0% | 0.0% | 100.0% |
| mistral-small3.2:24b-instruct-2506-fp16 | 1228 | 28 | 59563.8% | 48.5% | 50.0% | 0.0% | 100.0% |
| deepseek-r1:32b | 1228 | 205 | 43656.22% | 35.55% | 25.0% | 0.0% | 100.0% |
| mistral-medium-3.5:latest | 1228 | 51 | 25121.19% | 20.46% | 16.67% | 0.0% | 100.0% |
| gpt-oss:120b | 1228 | 30 | 25074.55% | 20.42% | 16.67% | 0.0% | 100.0% |
| nemotron-3-super:latest | 1228 | 142 | 27327.17% | 22.25% | 0.0% | 0.0% | 100.0% |
| qwen3.6:35b | 1228 | 86 | 17405.34% | 14.17% | 0.0% | 0.0% | 100.0% |
| gemma4:12b | 1228 | 20 | 16885.12% | 13.75% | 0.0% | 0.0% | 100.0% |
| igorls/gemma4-e4b-classifier:q8_0 | 1228 | 3 | 13673.89% | 11.14% | 0.0% | 0.0% | 100.0% |
| gemma4:e4b | 1228 | 2 | 13044.78% | 10.62% | 0.0% | 0.0% | 100.0% |
| qwen3.5:122b | 1228 | 14 | 12534.24% | 10.21% | 0.0% | 0.0% | 100.0% |
| gemma4:31b | 1228 | 6 | 9170.75% | 7.47% | 0.0% | 0.0% | 100.0% |
In this graph, triangles represent median values and circles represent average values.
In the following random example, Granite4 initially detected the following 4 tags:
credential-dumps: data-leaksfraud: scamsmoney-laundering:cashin/outponzy-financial gaian
However, when asked to validate these tags, the model rejected one of its own initial predictions:
"credential-dumps-data-leaks": {
"justification": "There are no mentions of stolen credentials, databases, or data breach disclosures.",
"match": false
}
This illustrates a direct contradiction between the model’s initial classification and its subsequent validation. Several models exhibited this form of instability: when asked only to return labels, they sometimes selected weakly supported tags that they later rejected when required to justify them against observable evidence. Requiring justifications therefore acts as a consistency check, not merely as explanatory output. The request for justification is done in a new session to not tamper the result with previous thought. This definitively almost double the processing time but increase the quality results.
Best model for tagging consensus
This score measures how well each model finds the consensus tags across all reports. The consensus tags are the tags most approved by validation, so they are treated as the expected important tags for a report. For each report, the model score is the percentage of consensus tags found during the initial tagging pass. A high score means the model usually finds the tags that validators agree are important. This metric mainly measures recall against consensus; it does not primarily measure whether the model added extra tags. Failed reports count as 0% and are counted in error reports.
| model | reports | error reports | sum | average | median | min | max |
|---|---|---|---|---|---|---|---|
| gemma4:31b | 1228 | 6 | 88264.02% | 71.88% | 71.43% | 0.0% | 100.0% |
| qwen3.5:122b | 1228 | 14 | 76407.69% | 62.22% | 62.5% | 0.0% | 100.0% |
| gemma4:12b | 1228 | 20 | 76101.7% | 61.97% | 62.5% | 0.0% | 100.0% |
| igorls/gemma4-e4b-classifier:q8_0 | 1228 | 3 | 76100.16% | 61.97% | 60.0% | 0.0% | 100.0% |
| gpt-oss:120b | 1228 | 30 | 66361.66% | 54.04% | 50.0% | 0.0% | 100.0% |
| gemma4:e4b | 1228 | 2 | 64389.1% | 52.43% | 50.0% | 0.0% | 100.0% |
| qwen3.6:35b | 1228 | 86 | 61581.75% | 50.15% | 50.0% | 0.0% | 100.0% |
| mistral-medium-3.5:latest | 1228 | 51 | 58538.85% | 47.67% | 50.0% | 0.0% | 100.0% |
| nemotron-3-super:latest | 1228 | 142 | 43105.67% | 35.1% | 33.33% | 0.0% | 100.0% |
| mistral-small3.2:24b-instruct-2506-fp16 | 1228 | 28 | 33578.84% | 27.34% | 25.0% | 0.0% | 100.0% |
| deepseek-r1:32b | 1228 | 205 | 32607.81% | 26.55% | 25.0% | 0.0% | 100.0% |
| granite4.1:30b | 1228 | 78 | 32290.26% | 26.29% | 25.0% | 0.0% | 100.0% |
| devstral-2:latest | 1228 | 190 | 26107.5% | 21.26% | 22.22% | 0.0% | 100.0% |
Best model that do not overtag
This score measures whether each model finds the right tags without adding too many wrong or useless tags. For each report, precision is validated tags / proposed tags, so it drops when the model proposes false or irrelevant tags. Recall is consensus tags found / total consensus tags, so it drops when the model misses important expected tags. The displayed score is F1: 2 * precision * recall / (precision + recall). This balances tag quality and tag coverage: a model ranks well only when it keeps high precision and high recall. Failed reports count as 0% and are counted in error reports.
| model | reports | error reports | sum | average | median | min | max |
|---|---|---|---|---|---|---|---|
| gemma4:31b | 1228 | 6 | 97115.54% | 79.08% | 80.0% | 0.0% | 100.0% |
| qwen3.5:122b | 1228 | 14 | 87287.6% | 71.08% | 73.69% | 0.0% | 100.0% |
| gemma4:12b | 1228 | 20 | 85060.39% | 69.27% | 72.72% | 0.0% | 100.0% |
| igorls/gemma4-e4b-classifier:q8_0 | 1228 | 3 | 87116.33% | 70.94% | 72.0% | 0.0% | 100.0% |
| gemma4:e4b | 1228 | 2 | 78248.62% | 63.72% | 66.67% | 0.0% | 100.0% |
| qwen3.6:35b | 1228 | 86 | 75338.55% | 61.35% | 66.67% | 0.0% | 100.0% |
| gpt-oss:120b | 1228 | 30 | 76082.63% | 61.96% | 63.16% | 0.0% | 100.0% |
| mistral-medium-3.5:latest | 1228 | 51 | 70868.69% | 57.71% | 60.0% | 0.0% | 100.0% |
| nemotron-3-super:latest | 1228 | 142 | 56134.32% | 45.71% | 50.0% | 0.0% | 100.0% |
| deepseek-r1:32b | 1228 | 205 | 39643.9% | 32.28% | 33.33% | 0.0% | 100.0% |
| mistral-small3.2:24b-instruct-2506-fp16 | 1228 | 28 | 39004.08% | 31.76% | 32.43% | 0.0% | 100.0% |
| granite4.1:30b | 1228 | 78 | 37285.18% | 30.36% | 30.77% | 0.0% | 100.0% |
| devstral-2:latest | 1228 | 190 | 34585.3% | 28.16% | 30.77% | 0.0% | 85.72% |
Best model for tagging
This score measures how reliable each model’s proposed tags are across all reports. For each report, the score is the percentage of the model’s proposed tags that validation confirmed as true: validated tags / proposed tags. A high score means the model avoids false positives and usually proposes tags that validators accept. This is different from Best model for tagging consensus, which measures whether the model found the expected consensus tags. This metric does not care whether a tag is part of the consensus: a non-consensus tag is not penalized if validation confirms it as true. It is penalized if validation rejects it, or if it is not confirmed as true. Failed reports are kept in the table with a 0% score and counted in error reports.
| model | reports | error reports | sum | average | median | min | max |
|---|---|---|---|---|---|---|---|
| gemma4:31b | 1228 | 6 | 113618.14% | 92.52% | 100.0% | 0.0% | 100.0% |
| qwen3.5:122b | 1228 | 14 | 109968.27% | 89.55% | 100.0% | 0.0% | 100.0% |
| gemma4:e4b | 1228 | 2 | 109401.18% | 89.09% | 100.0% | 0.0% | 100.0% |
| igorls/gemma4-e4b-classifier:q8_0 | 1228 | 3 | 108675.43% | 88.5% | 100.0% | 0.0% | 100.0% |
| qwen3.6:35b | 1228 | 86 | 105341.33% | 85.78% | 100.0% | 0.0% | 100.0% |
| gemma4:12b | 1228 | 20 | 105209.72% | 85.68% | 100.0% | 0.0% | 100.0% |
| nemotron-3-super:latest | 1228 | 142 | 93906.92% | 76.47% | 100.0% | 0.0% | 100.0% |
| mistral-medium-3.5:latest | 1228 | 51 | 97678.81% | 79.54% | 83.33% | 0.0% | 100.0% |
| gpt-oss:120b | 1228 | 30 | 97525.45% | 79.42% | 83.33% | 0.0% | 100.0% |
| deepseek-r1:32b | 1228 | 205 | 66673.3% | 54.29% | 60.0% | 0.0% | 100.0% |
| devstral-2:latest | 1228 | 190 | 60710.18% | 49.44% | 50.0% | 0.0% | 100.0% |
| mistral-small3.2:24b-instruct-2506-fp16 | 1228 | 28 | 58199.77% | 47.39% | 50.0% | 0.0% | 100.0% |
| granite4.1:30b | 1228 | 78 | 52524.37% | 42.77% | 40.0% | 0.0% | 100.0% |
Detection time
Initial tagging latency. This aggregates elapsed_second_request from classification.md for each model and shows average, minimum, and maximum request time. Failed model outputs are not counted.
| model | reports | sum | average | median | min | max |
|---|---|---|---|---|---|---|
| mistral-small3.2:24b-instruct-2506-fp16 | 1228 | 10099.54s | 8.22s | 7.6s | 1.03s | 88.58s |
| gpt-oss:120b | 1228 | 11209.34s | 9.13s | 8.73s | 1.56s | 51.41s |
| granite4.1:30b | 1227 | 13957.44s | 11.38s | 10.28s | 0.98s | 81.78s |
| gemma4:e4b | 1228 | 18003.37s | 14.66s | 12.94s | 1.79s | 51.67s |
| igorls/gemma4-e4b-classifier:q8_0 | 1227 | 21024.0s | 17.13s | 15.09s | 4.56s | 87.7s |
| nemotron-3-super:latest | 1224 | 28251.96s | 23.08s | 19.2s | 5.38s | 163.8s |
| devstral-2:latest | 1223 | 28817.15s | 23.56s | 21.23s | 2.29s | 228.98s |
| deepseek-r1:32b | 1221 | 34312.91s | 28.1s | 22.75s | 6.31s | 196.73s |
| mistral-medium-3.5:latest | 1227 | 31854.21s | 25.96s | 24.18s | 3.2s | 229.92s |
| qwen3.6:35b | 1224 | 34395.08s | 28.1s | 26.46s | 7.65s | 85.45s |
| gemma4:12b | 1215 | 60486.05s | 49.78s | 46.75s | 10.18s | 132.62s |
| gemma4:31b | 1226 | 59551.09s | 48.57s | 46.86s | 15.7s | 123.06s |
| qwen3.5:122b | 1225 | 80532.59s | 65.74s | 64.91s | 22.96s | 117.49s |
Validation time
Validation latency. This aggregates elapsed_second_request from validation.md for each model and shows average, minimum, and maximum request time. Failed validation outputs are not counted.
| model | reports | sum | average | median | min | max |
|---|---|---|---|---|---|---|
| gpt-oss:120b | 1198 | 16500.77s | 13.77s | 13.48s | 1.49s | 44.97s |
| igorls/gemma4-e4b-classifier:q8_0 | 1225 | 22154.78s | 18.09s | 17.85s | 5.35s | 81.2s |
| gemma4:e4b | 1226 | 24334.82s | 19.85s | 19.51s | 6.26s | 48.73s |
| mistral-small3.2:24b-instruct-2506-fp16 | 1209 | 26068.47s | 21.56s | 20.36s | 6.84s | 118.61s |
| granite4.1:30b | 1150 | 33812.38s | 29.4s | 27.26s | 3.87s | 152.02s |
| deepseek-r1:32b | 1036 | 32746.27s | 31.61s | 30.23s | 11.24s | 146.12s |
| gemma4:12b | 1208 | 43274.45s | 35.82s | 33.27s | 11.05s | 132.6s |
| qwen3.6:35b | 1142 | 41476.32s | 36.32s | 35.92s | 16.89s | 71.03s |
| nemotron-3-super:latest | 1087 | 56190.1s | 51.69s | 46.33s | 10.89s | 556.88s |
| qwen3.5:122b | 1214 | 69319.0s | 57.1s | 53.86s | 14.52s | 349.36s |
| gemma4:31b | 1222 | 68817.68s | 56.32s | 54.56s | 17.56s | 149.87s |
| devstral-2:latest | 1038 | 68552.81s | 66.04s | 61.9s | 25.37s | 334.14s |
| mistral-medium-3.5:latest | 1177 | 99965.29s | 84.93s | 81.46s | 17.28s | 489.91s |
Discussion
The strongest result is the consistent performance of gemma4:31b. It combined high precision, high consensus recall, low failure count, and substantially better balanced quality than the other evaluated models.
The experiments also support a multi-model workflow. Initial tagging can prioritize a high-quality model, while faster models can be used for validation, cross-checking, or high-volume pre-filtering. Consensus provides a practical way to reduce dependence on a single model, although it is not equivalent to ground-truth annotation.
The evaluation further shows that output control is a core part of LLM classification. Prompting for a restricted format helps, but exact parsing, identifier validation, timeout handling, and correction logic remain necessary components of the system.
Limitations
The consensus used in this study is model-derived rather than human-annotated. It can therefore reproduce shared model biases or taxonomy ambiguities. Consensus recall should be understood as agreement with the evaluation procedure, not as absolute classification accuracy.
The reports are also evaluated using collected text samples. Results may change with sample size, language distribution, channel topic, prompt design, model quantization, hardware, and inference configuration.
Finally, aggregate averages hide per-language and per-category differences. Future work should report stratified results, manually reviewed reference subsets, confidence calibration, and the cost of false positives versus false negatives.
Future work
The experiments nevertheless produced a useful labeled dataset and a solid evaluation baseline. Future evaluations should include newly released open-weight models and rerun the benchmark regularly as model families, context handling, and quantization methods evolve. Future work will also investigate lightweight models and alternative approaches, including RoBERTa-based classifiers and more traditional machine-learning triage methods. These approaches may preserve much of the observed classification quality while reducing inference time and the cost of justification.
DarkGram’s dataset and framework are public, with source code available on GitHub. They provide a useful external dataset and baseline for future evaluation of our taxonomy and lightweight classifiers.
Conclusion
LLMs can support large-scale messaging-channel classification, but useful deployment requires more than selecting a model with a high average score. The system must combine a controlled taxonomy, structured outputs, automated validation, failure handling, and a strategy for managing disagreement.
In this evaluation, gemma4:31b provided the best overall balance between tag quality and robustness. Smaller or faster models remained useful for validation and high-throughput processing. The results suggest that an agent-style pipeline based on independent tagging and cross-validation is a viable approach for reducing manual exposure and scaling classification across heterogeneous communication channels.
However, LLM processing time remains the main operational limitation. Average initial-tagging latency ranged from 8.22 to 65.74 seconds per model and channel, while validation ranged from 13.77 to 84.93 seconds. For the best-performing model, gemma4:31b, initial tagging required 48.57 seconds on average and validation required another 56.32 seconds. Because the protocol queries multiple models and requests tag justifications, these delays accumulate rapidly across 1,228 channel reports.
The resulting quality is promising, but the current processing time is incompatible with real-time monitoring or frequent large-scale reclassification. A production system will require staged filtering, caching, asynchronous processing, fewer validation calls, or a lightweight first-pass classifier before invoking an LLM.
This initial allow us to publish an open source engine API managing queuing and access.
The code is available on GitHub at https://github.com/AIPITCH/topic-classifier
CIRCL operate one instance of Topic Classifier. This services is offered on request. Access can be granted by contacting info@circl.lu.
AIPITCH
AI-Powered Innovative Toolkit for Cybersecurity Hubs (AIPITCH) will provide a comprehensive set of practical, AI-powered tools for operational teams responsible for cyberdefence.
tThe project focuses especially on Security Operations Centres with national responsibility. Its integrated toolkit will also support teams protecting other constituencies—including enterprise SOCs—so they can strengthen key services, turn diverse signals into actionable intelligence and respond more effectively.
Conference
We are pleased to share that the research presented in this post has been selected for presentation at the Underground Economy 2026 (UE26) Conference, taking place in Strasbourg, France. UE26 brings together an international community of cybersecurity, threat intelligence and law-enforcement practitioners working on cybercrime and operational security challenges.
