mirror of
https://github.com/opencv/opencv.git
synced 2026-09-11 21:01:33 -05:00
Re-enable & triage DISABLED tests for DNN module - #29702 Requires: https://github.com/opencv/opencv_extra/pull/1403 **co-authored by: @varun-jaiswal17** ### PR Changes: ## dnn test cleanup: re-enable stale-disabled tests, fix defect-blind tests, remove redundant coverage ### Removed (dead or unbuildable) - `test_int8_layers.cpp` (1118 lines, removed entirely): cannot compile — `Net::quantize()`, `getInputDetails`/`getOutputDetails` are all gone from `dnn.hpp`/`dnn/src`. Disabled in that same PR (#24980) because on-the-fly quantization was removed — every test in this file called `net.quantize()` to calibrate and run its own int8 conversion. Its own header comment said restore "when test models are quantized outside OpenCV". Pre-quantized ONNX/TFLite test models already do that. ### Removed (redundant or assertion-free) - `Tokenizer_BPE.Tokenizer_GPT2_Model`: line-for-line subset of `Tokenizer_GPT2` — same config, same input, same roundtrip assertion. - `Test_TensorFlow.read_inception`: printed `out.dims` and asserted nothing about the result; `inception_accuracy` loads the same `.pb` and checks it against a reference. - `Test_Caffe_nets` fixture + `INSTANTIATE`: registered **zero** `TEST_P` cases — dead scaffolding for Faster R-CNN tests removed earlier. - `Test_ONNX_nets.Squeezenet`: kernels {1×1, 3×3} and every op type already covered by dedicated layer tests. - `Test_ONNX_nets.VGG16_bn`: single conv kernel (3×3), fully covered by dedicated layer tests; skipped by default anyway under `mem_6gb`. - `Test_ONNX_nets.CaffeNet`: identical op multiset, node count (24) and conv signatures to retained `Alexnet`. - `Test_ONNX_nets.RCNN_ILSVRC13`: `Alexnet` minus `Softmax` (23 vs 24 nodes), identical conv signatures. - `Test_ONNX_nets.Inception_v1`: same op set as retained `Googlenet` (+1 `Reshape`) — Inception v1 *is* GoogLeNet. ### Given real assertions instead of stale expectations - `Test_ONNX_layers.Elementwise_Sqrt`: moved `testONNXModels("sqrt")` below `#endif` — its only work line sat inside `INF_ENGINE_VER_MAJOR_LT(2021040000)`, so without OpenVINO the body compiled to nothing and reported `[ OK ]` on all 3 backends. - `Layer_Test_01D.Clip`: now calls `ClipLayer::create` with `"min"`/`"max"` — it set `lp.type = "Clip"` but constructed `ReLU6Layer::create`, and `runLayer` never reads `layer->type`, so it just re-ran `ReLU6`. - `Layer_Arg_Test`: removed the "disabled" comment, corrected the `convertTo` comment — the comment said the test was disabled while it runs 8 cases, and the second said "convert to float" where the code converts to `CV_64S`. ### Re-enabled as-is (stale disable reasons) - `Test_ONNX_layers.LSTM`/`LSTM_bidirectional` (`test_onnx_importer.cpp:1551,1558`): disabled by #21522 (2022) for poor 1-D-mat handling in the importer of that era; no longer reproduces. - `Test_ONNX_layers.Split_sizes_0d` (`:1373`): disabled by #22652 for a Mul/0-d-tensor shape ambiguity (A×1 vs 1×A); dnn now supports real 1-D Mats, so the output matches the reference exactly. - `DNNTestNetwork.YOLOv8n` ### Library fixes found while re-enabling - `Test_ONNX_layers.LSTM_layout_seq`/`LSTM_layout_batch` (`test_onnx_importer.cpp:1721,1728`): `LSTM2` never transposed `X` for ONNX `layout=1` (batch-first); fixed via `transposeND` gated on `layout==BATCH_SEQ_HID` (`recurrent2_layers.cpp:172`). Fixture also had a leaked loop variable that made the reference a copy of the input; rebuilt by hand since ORT itself refuses to run `layout=1`. - `Test_Graph_Simplifier.ResizeSubgraph` (`test_graph_simplifier.cpp:61`): disabled by the block-layout PR #28585; expectations updated for the `TransformLayout` pass that PR introduced. The test now covers 4 subgraphs rather than 6, because `GatherCastSubgraph` and `MulCastSubgraph` were removed by `0e36cafcf4` and `7669897910` (`Gather`/`Mul` -> `Cast` is no longer fused, since folding it away silently dropped the `Cast`'s dtype semantics). The dynamic-scale `Shape`/`Gather`/`Cast`/`Floor`/`Concat`/`Unsqueeze`/`Slice` chain these models use to compute Resize's scale factor therefore no longer collapses, and the `Mul` survives as `NaryEltwise`, which is why the expected layer lists grew ### Deliberately kept - `ZFNet`: its **7×7** conv appears in no dedicated layer test, and its kernel set {7×7, 5×5, 3×3} differs from `Alexnet`'s {11×11, 5×5, 3×3}. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
233 lines
8.6 KiB
C++
233 lines
8.6 KiB
C++
// This file is part of OpenCV project.
|
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
|
// of this distribution and at http://opencv.org/license.html.
|
|
|
|
#include "test_precomp.hpp"
|
|
|
|
namespace opencv_test { namespace {
|
|
|
|
template<typename TString>
|
|
static String _tf(TString filename) {
|
|
String basetestdir = getOpenCVExtraDir();
|
|
size_t len = basetestdir.size();
|
|
if(len > 0 && basetestdir[len-1] != '/' && basetestdir[len-1] != '\\')
|
|
return (basetestdir + "/dnn/llm") + filename;
|
|
return (basetestdir + "dnn/llm/") + filename;
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_GPT2_Tokens) {
|
|
std::string gpt2_model = _tf("gpt2/config.json");
|
|
Tokenizer tok = Tokenizer::load(gpt2_model);
|
|
std::vector<int> tokens = tok.encode("hello world");
|
|
std::vector<int> expected = {31373, 995};
|
|
EXPECT_EQ(tokens, expected);
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_GPT4) {
|
|
std::string gpt4_model = _tf("gpt4/config.json");
|
|
Tokenizer tok = Tokenizer::load(gpt4_model);
|
|
|
|
std::vector<int> tokens = tok.encode("hello world");
|
|
std::vector<int> expected = {15339, 1917};
|
|
EXPECT_EQ(tokens, expected);
|
|
|
|
std::string sent = tok.decode({15339, 1917});
|
|
std::string expec_str = "hello world";
|
|
EXPECT_EQ(sent, expec_str);
|
|
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_GPT2) {
|
|
std::string gpt2_model = _tf("gpt2/config.json");
|
|
Tokenizer tok = Tokenizer::load(gpt2_model);
|
|
auto ids = tok.encode("hello world");
|
|
for (auto id : ids) std::cout << id << " ";
|
|
std::cout << std::endl;
|
|
auto txt = tok.decode(ids);
|
|
EXPECT_EQ(txt, "hello world");
|
|
|
|
// "Long characters" in Chinese
|
|
auto ids_j = tok.encode("\xe9\x95\xbf\xe5\xad\x97\xe7\xac\xa6");
|
|
std::string word = tok.decode(ids_j);
|
|
std::cout << word << std::endl;
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, SimpleRepeated_GPT2) {
|
|
Tokenizer gpt2_tok = Tokenizer::load(_tf("gpt2/config.json"));
|
|
EXPECT_EQ(gpt2_tok.encode("0"), std::vector<int>({15}));
|
|
EXPECT_EQ(gpt2_tok.encode("00"), std::vector<int>({405}));
|
|
EXPECT_EQ(gpt2_tok.encode("000"), std::vector<int>({830}));
|
|
EXPECT_EQ(gpt2_tok.encode("0000"), std::vector<int>({2388}));
|
|
EXPECT_EQ(gpt2_tok.encode("00000"), std::vector<int>({20483}));
|
|
EXPECT_EQ(gpt2_tok.encode("000000"), std::vector<int>({10535}));
|
|
EXPECT_EQ(gpt2_tok.encode("0000000"), std::vector<int>({24598}));
|
|
EXPECT_EQ(gpt2_tok.encode("00000000"), std::vector<int>({8269}));
|
|
EXPECT_EQ(gpt2_tok.encode("000000000"), std::vector<int>({10535, 830}));
|
|
EXPECT_EQ(gpt2_tok.encode("0000000000"), std::vector<int>({8269, 405}));
|
|
EXPECT_EQ(gpt2_tok.encode("00000000000"), std::vector<int>({8269, 830}));
|
|
EXPECT_EQ(gpt2_tok.encode("000000000000"), std::vector<int>({8269, 2388}));
|
|
EXPECT_EQ(gpt2_tok.encode("0000000000000"), std::vector<int>({8269, 20483}));
|
|
EXPECT_EQ(gpt2_tok.encode("00000000000000"), std::vector<int>({8269, 10535}));
|
|
EXPECT_EQ(gpt2_tok.encode("000000000000000"), std::vector<int>({8269, 24598}));
|
|
EXPECT_EQ(gpt2_tok.encode("0000000000000000"), std::vector<int>({25645}));
|
|
EXPECT_EQ(gpt2_tok.encode("00000000000000000"), std::vector<int>({8269, 10535, 830}));
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, CatastrophicallyRepetitive_GPT2) {
|
|
Tokenizer gpt2_tok = Tokenizer::load(_tf("gpt2/config.json"));
|
|
std::vector<std::string> chars = {"^", "0", "a", "'s", " ", "\n"};
|
|
for (const auto& c : chars) {
|
|
std::string big_value(c.size() == 1 ? 10000 : 10000 * c.size(), c[0]);
|
|
if (c == "'s") big_value = std::string(10000, '\'') + std::string(10000, 's');
|
|
EXPECT_EQ(big_value, gpt2_tok.decode(gpt2_tok.encode(big_value)));
|
|
|
|
std::string with_space = " " + big_value;
|
|
EXPECT_EQ(with_space, gpt2_tok.decode(gpt2_tok.encode(with_space)));
|
|
|
|
std::string with_newline = big_value + "\n";
|
|
EXPECT_EQ(with_newline, gpt2_tok.decode(gpt2_tok.encode(with_newline)));
|
|
}
|
|
}
|
|
|
|
// ---- Qwen2.5 tests ----
|
|
// Ground truth generated with:
|
|
// from transformers import AutoTokenizer
|
|
// tok = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B")
|
|
// tok.encode(text)
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_Qwen2_5_English) {
|
|
std::string model = _tf("qwen2.5/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("Hello world"), (std::vector<int>{9707, 1879}));
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_Qwen2_5_Chinese) {
|
|
std::string model = _tf("qwen2.5/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
// 你好世界
|
|
EXPECT_EQ(tok.encode("\xe4\xbd\xa0\xe5\xa5\xbd\xe4\xb8\x96\xe7\x95\x8c"),
|
|
(std::vector<int>{108386, 99489}));
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_Qwen2_5_Code) {
|
|
std::string model = _tf("qwen2.5/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("def hello(): print('hello')"),
|
|
(std::vector<int>{750, 23811, 4555, 1173, 492, 14990, 863}));
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_Qwen2_5_Numbers) {
|
|
std::string model = _tf("qwen2.5/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("2024"), (std::vector<int>{17, 15, 17, 19}));
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_Qwen2_5_SpecialTokens) {
|
|
std::string model = _tf("qwen2.5/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
// <|im_start|>user\nHello<|im_end|>
|
|
EXPECT_EQ(tok.encode("<|im_start|>user\nHello<|im_end|>"),
|
|
(std::vector<int>{151644, 872, 198, 9707, 151645}));
|
|
}
|
|
|
|
TEST(Tokenizer_BPE, Tokenizer_Qwen2_5_Roundtrip) {
|
|
std::string model = _tf("qwen2.5/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
std::vector<std::string> cases = {
|
|
"Hello world",
|
|
"def hello(): print('hello')",
|
|
"2024",
|
|
};
|
|
for (const auto& text : cases) {
|
|
EXPECT_EQ(tok.decode(tok.encode(text)), text);
|
|
}
|
|
}
|
|
|
|
|
|
TEST(Tokenizer_Gemma, Tokenizer_Gemma3_English) {
|
|
std::string model = _tf("gemma3/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("Hello world"), (std::vector<int>{9259, 1902}));
|
|
}
|
|
|
|
TEST(Tokenizer_Gemma, Tokenizer_Gemma3_Phrase) {
|
|
std::string model = _tf("gemma3/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("the quick brown fox"),
|
|
(std::vector<int>{1437, 3823, 8864, 37423}));
|
|
}
|
|
|
|
TEST(Tokenizer_Gemma, Tokenizer_Gemma3_Mixed) {
|
|
std::string model = _tf("gemma3/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("OpenCV"), (std::vector<int>{7084, 20741}));
|
|
}
|
|
|
|
TEST(Tokenizer_Gemma, Tokenizer_Gemma3_Numbers) {
|
|
std::string model = _tf("gemma3/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("2024"), (std::vector<int>{236778, 236771, 236778, 236812}));
|
|
}
|
|
|
|
TEST(Tokenizer_Gemma, Tokenizer_Gemma3_SpecialTokens) {
|
|
std::string model = _tf("gemma3/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("<bos>Hello<eos>"), (std::vector<int>{2, 9259, 1}));
|
|
}
|
|
|
|
TEST(Tokenizer_Gemma, Tokenizer_Gemma3_Roundtrip) {
|
|
std::string model = _tf("gemma3/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
std::vector<std::string> cases = {
|
|
"Hello world",
|
|
"the quick brown fox",
|
|
"OpenCV",
|
|
"hello world",
|
|
};
|
|
for (const auto& text : cases) {
|
|
EXPECT_EQ(tok.decode(tok.encode(text)), text);
|
|
}
|
|
}
|
|
|
|
// Gemma2 tests (SentencePiece tokenizer)
|
|
TEST(Tokenizer_SentencePiece, Tokenizer_Gemma2_English) {
|
|
std::string model = _tf("gemma2/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("Hello world"), (std::vector<int>{2, 4521, 2134}));
|
|
}
|
|
|
|
TEST(Tokenizer_SentencePiece, Tokenizer_Gemma2_Phrase) {
|
|
std::string model = _tf("gemma2/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("the quick brown fox"),
|
|
(std::vector<int>{2, 1175, 4320, 8426, 25341}));
|
|
}
|
|
|
|
TEST(Tokenizer_SentencePiece, Tokenizer_Gemma2_Mixed) {
|
|
std::string model = _tf("gemma2/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("OpenCV"), (std::vector<int>{2, 6047, 17813}));
|
|
}
|
|
|
|
TEST(Tokenizer_SentencePiece, Tokenizer_Gemma2_Numbers) {
|
|
std::string model = _tf("gemma2/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
EXPECT_EQ(tok.encode("2024"), (std::vector<int>{2, 235284, 235276, 235284, 235310}));
|
|
}
|
|
|
|
TEST(Tokenizer_SentencePiece, Tokenizer_Gemma2_Roundtrip) {
|
|
std::string model = _tf("gemma2/config.json");
|
|
Tokenizer tok = Tokenizer::load(model);
|
|
std::vector<std::string> cases = {
|
|
"Hello world",
|
|
"the quick brown fox",
|
|
"OpenCV",
|
|
"hello world",
|
|
};
|
|
for (const auto& text : cases) {
|
|
EXPECT_EQ(tok.decode(tok.encode(text)), text);
|
|
}
|
|
}
|
|
|
|
}}
|