From cd2a70e385e4ced5c5b1b6e52d05a6e61faeb7d2 Mon Sep 17 00:00:00 2001 From: Zhong Hui Date: Tue, 18 Jun 2024 18:49:32 +0800 Subject: [PATCH] [PaddleNLP 3.0] Refactor/3 part1- remove fast tokenizer. (#8613) * delete fast_tokenizer. * delete deploy with fast_tokenizer. * delete fast tokenizer in tests. --- .github/workflows/fast_tokenizer.yml | 48 - README.md | 12 - README_en.md | 12 - .../information_extraction/taskflow_doc.md | 1 - .../information_extraction/taskflow_doc_en.md | 4 +- .../information_extraction/taskflow_text.md | 4 +- .../taskflow_text_en.md | 4 +- .../deploy/paddle_serving/README.md | 9 - .../hierarchical/deploy/predictor/README.md | 8 - .../deploy/triton_serving/README.md | 10 - .../deploy/triton_serving/README.md | 10 - .../deploy/paddle_serving/README.md | 9 - .../multi_label/deploy/predictor/README.md | 7 - .../deploy/triton_serving/README.md | 10 - .../deploy/python/README.md | 8 - ...enlp.transformers.bert.faster_tokenizer.po | 23 - ...nlp.transformers.ernie.faster_tokenizer.po | 23 - ...lenlp.transformers.bert.fast_tokenizer.rst | 7 - docs/source/paddlenlp.transformers.bert.rst | 1 - ...enlp.transformers.ernie.fast_tokenizer.rst | 7 - docs/source/paddlenlp.transformers.ernie.rst | 1 - ....transformers.ernie_m.faster_tokenizer.rst | 7 - .../source/paddlenlp.transformers.ernie_m.rst | 1 - ...p.transformers.tinybert.fast_tokenizer.rst | 7 - .../paddlenlp.transformers.tinybert.rst | 1 - .../language_model/roberta/create_data.py | 7 +- fast_tokenizer/CMakeLists.txt | 235 -- fast_tokenizer/FastTokenizer.cmake.in | 44 - fast_tokenizer/LICENSE | 203 -- fast_tokenizer/Makefile | 38 - fast_tokenizer/README.md | 150 - fast_tokenizer/cmake/ByproductsICU.cmake | 54 - fast_tokenizer/cmake/FindNumPy.cmake | 52 - fast_tokenizer/cmake/dummy.c.in | 3 - fast_tokenizer/cmake/external/dart.cmake | 45 - fast_tokenizer/cmake/external/gflags.cmake | 121 - fast_tokenizer/cmake/external/glog.cmake | 117 - fast_tokenizer/cmake/external/gtest.cmake | 85 - fast_tokenizer/cmake/external/icu.cmake | 138 - .../cmake/external/nlohmann_json.cmake | 46 - fast_tokenizer/cmake/external/protobuf.cmake | 295 -- fast_tokenizer/cmake/external/pybind11.cmake | 45 - fast_tokenizer/cmake/external/python.cmake | 74 - fast_tokenizer/cmake/external/re2.cmake | 108 - fast_tokenizer/cmake/external/utf8proc.cmake | 51 - fast_tokenizer/cmake/generic.cmake | 208 -- fast_tokenizer/cmake/python_module.cmake | 54 - fast_tokenizer/cmake/third_party.cmake | 32 - fast_tokenizer/docs/compile/README.md | 16 - .../docs/compile/how_to_build_android.md | 46 - .../compile/how_to_build_linux_and_mac.md | 36 - .../docs/compile/how_to_build_windows.md | 42 - fast_tokenizer/docs/cpp/README.md | 71 - fast_tokenizer/docs/pipeline/README.md | 254 -- fast_tokenizer/docs/python/README.md | 16 - fast_tokenizer/examples/clip/README.md | 0 .../examples/clip/cpp/CMakeLists.txt | 28 - fast_tokenizer/examples/clip/cpp/README.md | 99 - fast_tokenizer/examples/clip/cpp/demo.cc | 70 - fast_tokenizer/examples/clip/python/README.md | 0 fast_tokenizer/examples/clip/python/demo.py | 13 - fast_tokenizer/examples/ernie-3.0/README.md | 21 - .../examples/ernie-3.0/cpp/CMakeLists.txt | 22 - .../examples/ernie-3.0/cpp/README.md | 0 fast_tokenizer/examples/ernie-3.0/cpp/demo.cc | 70 - .../examples/ernie-3.0/python/README.md | 0 .../examples/ernie-3.0/python/demo.py | 26 - fast_tokenizer/fast_tokenizer/CMakeLists.txt | 44 - .../fast_tokenizer/core/CMakeLists.txt | 4 - .../fast_tokenizer/core/added_vocabulary.cc | 424 --- .../fast_tokenizer/core/added_vocabulary.h | 154 - fast_tokenizer/fast_tokenizer/core/base.cc | 53 - fast_tokenizer/fast_tokenizer/core/base.h | 378 --- .../fast_tokenizer/core/encoding.cc | 669 ---- fast_tokenizer/fast_tokenizer/core/encoding.h | 135 - .../fast_tokenizer/core/tokenizer.cc | 876 ----- .../fast_tokenizer/core/tokenizer.h | 254 -- .../fast_tokenizer/decoders/CMakeLists.txt | 1 - .../fast_tokenizer/decoders/decoder.h | 32 - .../fast_tokenizer/decoders/decoders.h | 18 - .../fast_tokenizer/decoders/wordpiece.cc | 69 - .../fast_tokenizer/decoders/wordpiece.h | 42 - .../fast_tokenizer/models/CMakeLists.txt | 3 - fast_tokenizer/fast_tokenizer/models/bpe.cc | 349 -- fast_tokenizer/fast_tokenizer/models/bpe.h | 82 - .../fast_tokenizer/models/fast_wordpiece.cc | 428 --- .../fast_tokenizer/models/fast_wordpiece.h | 95 - fast_tokenizer/fast_tokenizer/models/model.h | 39 - fast_tokenizer/fast_tokenizer/models/models.h | 21 - .../fast_tokenizer/models/unigram.cc | 436 --- .../fast_tokenizer/models/unigram.h | 86 - .../fast_tokenizer/models/wordpiece.cc | 294 -- .../fast_tokenizer/models/wordpiece.h | 88 - .../fast_tokenizer/normalizers/CMakeLists.txt | 5 - .../fast_tokenizer/normalizers/bert.cc | 120 - .../fast_tokenizer/normalizers/bert.h | 47 - .../fast_tokenizer/normalizers/normalizer.cc | 656 ---- .../fast_tokenizer/normalizers/normalizer.h | 205 -- .../fast_tokenizer/normalizers/normalizers.h | 23 - .../fast_tokenizer/normalizers/precompiled.cc | 87 - .../fast_tokenizer/normalizers/precompiled.h | 44 - .../fast_tokenizer/normalizers/replace.cc | 51 - .../fast_tokenizer/normalizers/replace.h | 45 - .../fast_tokenizer/normalizers/strip.cc | 68 - .../fast_tokenizer/normalizers/strip.h | 51 - .../fast_tokenizer/normalizers/unicode.cc | 103 - .../fast_tokenizer/normalizers/unicode.h | 57 - .../fast_tokenizer/normalizers/utils.cc | 158 - .../fast_tokenizer/normalizers/utils.h | 50 - .../postprocessors/CMakeLists.txt | 1 - .../fast_tokenizer/postprocessors/bert.cc | 208 -- .../fast_tokenizer/postprocessors/bert.h | 43 - .../postprocessors/byte_level.cc | 74 - .../postprocessors/byte_level.h | 46 - .../postprocessors/postprocessor.cc | 37 - .../postprocessors/postprocessor.h | 41 - .../postprocessors/postprocessors.h | 21 - .../fast_tokenizer/postprocessors/roberta.cc | 244 -- .../fast_tokenizer/postprocessors/roberta.h | 47 - .../fast_tokenizer/postprocessors/template.cc | 454 --- .../fast_tokenizer/postprocessors/template.h | 191 -- .../pretokenizers/CMakeLists.txt | 4 - .../fast_tokenizer/pretokenizers/bert.cc | 74 - .../fast_tokenizer/pretokenizers/bert.h | 35 - .../pretokenizers/byte_level.cc | 150 - .../fast_tokenizer/pretokenizers/byte_level.h | 44 - .../fast_tokenizer/pretokenizers/metaspace.cc | 87 - .../fast_tokenizer/pretokenizers/metaspace.h | 48 - .../pretokenizers/pretokenizer.cc | 277 -- .../pretokenizers/pretokenizer.h | 117 - .../pretokenizers/pretokenizers.h | 24 - .../fast_tokenizer/pretokenizers/sequence.cc | 122 - .../fast_tokenizer/pretokenizers/sequence.h | 43 - .../fast_tokenizer/pretokenizers/split.cc | 72 - .../fast_tokenizer/pretokenizers/split.h | 48 - .../pretokenizers/whitespace.cc | 50 - .../fast_tokenizer/pretokenizers/whitespace.h | 34 - .../whitespace_and_punctuation.cc | 60 - .../whitespace_and_punctuation.h | 37 - .../fast_tokenizer/pybind/CMakeLists.txt | 10 - fast_tokenizer/fast_tokenizer/pybind/core.cc | 288 -- fast_tokenizer/fast_tokenizer/pybind/core.h | 27 - .../fast_tokenizer/pybind/decoders.cc | 74 - .../fast_tokenizer/pybind/decoders.h | 27 - .../fast_tokenizer/pybind/exception.cc | 35 - .../fast_tokenizer/pybind/exception.h | 45 - .../fast_tokenizer/pybind/models.cc | 551 ---- fast_tokenizer/fast_tokenizer/pybind/models.h | 27 - .../fast_tokenizer/pybind/normalizers.cc | 462 --- .../fast_tokenizer/pybind/normalizers.h | 27 - .../fast_tokenizer/pybind/postprocessors.cc | 418 --- .../fast_tokenizer/pybind/postprocessors.h | 27 - .../fast_tokenizer/pybind/pretokenizers.cc | 265 -- .../fast_tokenizer/pybind/pretokenizers.h | 27 - .../fast_tokenizer/pybind/pybind.cc | 51 - .../fast_tokenizer/pybind/tokenizers.cc | 1428 -------- .../fast_tokenizer/pybind/tokenizers.h | 27 - fast_tokenizer/fast_tokenizer/pybind/utils.cc | 277 -- fast_tokenizer/fast_tokenizer/pybind/utils.h | 108 - .../fast_tokenizer/test/CMakeLists.txt | 58 - .../fast_tokenizer/test/gtest_main.cc | 62 - .../test/test_bert_pretokenizer.cc | 49 - .../test/test_bert_tokenizer.cc | 120 - .../test/test_clip_fast_tokenizer.cc | 50 - .../test/test_ernie_fast_tokenizer.cc | 88 - .../test/test_fast_wordpiece.cc | 42 - .../fast_tokenizer/test/test_normalizer.cc | 55 - .../fast_tokenizer/test/test_replace.cc | 47 - .../test/test_roberta_postprocessor.cc | 94 - .../test/test_split_pretokenizer.cc | 110 - .../fast_tokenizer/test/test_strip.cc | 55 - .../fast_tokenizer/test/test_unicode.cc | 61 - .../fast_tokenizer/test/test_utils.cc | 37 - .../fast_tokenizer/test/test_whitespace.cc | 40 - .../fast_tokenizer/test/test_wordpiece.cc | 96 - fast_tokenizer/fast_tokenizer/test/utils.h | 37 - .../fast_tokenizer/tokenizers/CMakeLists.txt | 0 .../tokenizers/clip_fast_tokenizer.cc | 138 - .../tokenizers/clip_fast_tokenizer.h | 61 - .../tokenizers/ernie_fast_tokenizer.cc | 152 - .../tokenizers/ernie_fast_tokenizer.h | 70 - .../fast_tokenizer/utils/CMakeLists.txt | 5 - fast_tokenizer/fast_tokenizer/utils/cache.h | 102 - .../fast_tokenizer/utils/failure.cc | 425 --- fast_tokenizer/fast_tokenizer/utils/failure.h | 107 - .../fast_tokenizer/utils/lattice.cc | 546 ---- fast_tokenizer/fast_tokenizer/utils/lattice.h | 192 -- fast_tokenizer/fast_tokenizer/utils/path.h | 58 - .../utils/sentencepiece_normalizer.cc | 342 -- .../utils/sentencepiece_normalizer.h | 114 - .../fast_tokenizer/utils/shared_mutex.h | 304 -- .../fast_tokenizer/utils/string_view.h | 53 - fast_tokenizer/fast_tokenizer/utils/trie.cc | 231 -- fast_tokenizer/fast_tokenizer/utils/trie.h | 120 - .../fast_tokenizer/utils/unique_ptr.h | 61 - fast_tokenizer/fast_tokenizer/utils/utf8.h | 225 -- fast_tokenizer/fast_tokenizer/utils/utils.cc | 147 - fast_tokenizer/fast_tokenizer/utils/utils.h | 187 -- fast_tokenizer/fast_tokenizer/utils/variant.h | 2859 ----------------- fast_tokenizer/icu_filters.json | 35 - fast_tokenizer/perf/README.md | 68 - fast_tokenizer/perf/perf.py | 134 - fast_tokenizer/perf/requirements.txt | 3 - fast_tokenizer/perf/run_all_perf.sh | 27 - fast_tokenizer/python/CMakeLists.txt | 31 - .../python/fast_tokenizer/__init__.py | 63 - .../python/fast_tokenizer/c_wrap.py | 505 --- .../fast_tokenizer/decoders/__init__.py | 28 - .../python/fast_tokenizer/libs/__init__.py | 15 - .../python/fast_tokenizer/models/__init__.py | 169 - .../fast_tokenizer/normalizers/__init__.py | 103 - .../fast_tokenizer/postprocessors/__init__.py | 54 - .../fast_tokenizer/pretokenizers/__init__.py | 113 - .../tokenizers_impl/__init__.py | 18 - .../tokenizers_impl/base_tokenizer.py | 169 - .../fast_tokenizer/tokenizers_impl/clip.py | 101 - .../fast_tokenizer/tokenizers_impl/ernie.py | 110 - .../tokenizers_impl/sentencepiece_bpe.py | 56 - .../tests/test_byte_level_pretokenizer.py | 69 - .../python/tests/test_clip_tokenizer.py | 91 - .../python/tests/test_fast_wordpiece.py | 92 - .../python/tests/test_tokenizer_json.py | 85 - fast_tokenizer/run_build_android_armv7_lib.sh | 18 - .../run_build_android_armv7_lite_lib.sh | 18 - fast_tokenizer/run_build_android_armv8_lib.sh | 18 - .../run_build_android_armv8_lite_lib.sh | 18 - fast_tokenizer/run_build_cpp_lib.bat | 7 - fast_tokenizer/run_build_cpp_lib.sh | 36 - fast_tokenizer/run_build_py_lib.bat | 14 - fast_tokenizer/run_build_py_lib.sh | 44 - fast_tokenizer/run_fast_tokenizer_cpp_test.sh | 24 - fast_tokenizer/setup.py | 97 - .../tools/codestyle/clang_format.hook | 15 - fast_tokenizer/tools/codestyle/copyright.hook | 134 - .../tools/codestyle/cpplint_pre_commit.hook | 0 .../tools/codestyle/pylint_pre_commit.hook | 18 - .../deploy/paddle_serving/README.md | 8 - model_zoo/bert/README.md | 1 - model_zoo/bert/deploy/python/README.md | 11 - model_zoo/bert/deploy/python/seq_cls_infer.py | 8 +- model_zoo/ernie-1.0/README.md | 2 - model_zoo/ernie-1.0/finetune/deploy/README.md | 11 - .../finetune/deploy/seq_cls_infer.py | 8 +- model_zoo/ernie-1.0/run_gb512_s1m_static.sh | 60 - model_zoo/ernie-1.0/run_pretrain_static.py | 744 ----- model_zoo/ernie-3.0-tiny/README.md | 7 +- model_zoo/ernie-3.0-tiny/deploy/README.md | 2 - .../ernie-3.0-tiny/deploy/android/.gitignore | 23 - .../ernie-3.0-tiny/deploy/android/README.md | 260 -- .../deploy/android/app/build.gradle | 91 - .../deploy/android/app/proguard-rules.pro | 21 - .../android/app/src/main/AndroidManifest.xml | 30 - .../app/ernie_tiny/ERNIETinyMainActivity.java | 241 -- .../ernie_tiny/ERNIETinySettingsActivity.java | 133 - .../ernie_tiny/ERNIETinyWelcomeActivity.java | 30 - .../drawable-v24/ic_launcher_foreground.xml | 34 - .../res/drawable-v24/round_corner_btn.xml | 10 - .../src/main/res/drawable-xhdpi/back_btn.png | Bin 455 -> 0 bytes .../src/main/res/drawable-xhdpi/more_menu.png | Bin 414 -> 0 bytes .../src/main/res/drawable/btn_settings.xml | 6 - .../res/drawable/btn_settings_default.xml | 13 - .../res/drawable/btn_settings_pressed.xml | 13 - .../res/drawable/ic_launcher_background.xml | 170 - .../app/src/main/res/drawable/main_bk.png | Bin 594200 -> 0 bytes .../app/src/main/res/drawable/paddle_logo.png | Bin 5043 -> 0 bytes .../res/layout/ernie_tiny_activity_main.xml | 84 - .../main/res/layout/ernie_tiny_welcome.xml | 76 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2963 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4905 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2060 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2783 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4490 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 6895 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6387 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10413 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9128 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15132 -> 0 bytes .../app/src/main/res/values/arrays.xml | 60 - .../app/src/main/res/values/colors.xml | 25 - .../app/src/main/res/values/dimens.xml | 17 - .../app/src/main/res/values/strings.xml | 23 - .../app/src/main/res/values/styles.xml | 70 - .../app/src/main/res/values/values.xml | 17 - .../src/main/res/xml/ernie_tiny_settings.xml | 43 - .../deploy/android/build.gradle | 37 - .../deploy/android/ernie_tiny/.gitignore | 1 - .../deploy/android/ernie_tiny/build.gradle | 113 - .../android/ernie_tiny/consumer-rules.pro | 0 .../deploy/android/ernie_tiny/libs/.gitignore | 2 - .../android/ernie_tiny/proguard-rules.pro | 21 - .../ernie_tiny/ExampleInstrumentedTest.java | 25 - .../ernie_tiny/src/main/AndroidManifest.xml | 5 - .../ernie_tiny/src/main/cpp/CMakeLists.txt | 55 - .../src/main/cpp/ernie_tiny_jni/convert_jni.h | 171 - .../src/main/cpp/ernie_tiny_jni/perf_jni.h | 54 - .../main/cpp/ernie_tiny_jni/predictor_jni.cc | 546 ---- .../cpp/ernie_tiny_jni/runtime_option_jni.cc | 99 - .../cpp/ernie_tiny_jni/runtime_option_jni.h | 30 - .../paddlenlp/ernie_tiny/Initializer.java | 22 - .../IntentDetAndSlotFillResult.java | 25 - .../paddlenlp/ernie_tiny/LitePowerMode.java | 10 - .../paddlenlp/ernie_tiny/Predictor.java | 131 - .../paddlenlp/ernie_tiny/RuntimeOption.java | 68 - .../paddlenlp/ernie_tiny/ExampleUnitTest.java | 17 - .../deploy/android/gradle.properties | 13 - .../android/gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - .../ernie-3.0-tiny/deploy/android/gradlew | 185 -- .../ernie-3.0-tiny/deploy/android/gradlew.bat | 89 - .../deploy/android/local.properties | 8 - .../deploy/android/settings.gradle | 3 - .../deploy/android/ui/.gitignore | 1 - .../deploy/android/ui/build.gradle | 40 - .../deploy/android/ui/consumer-rules.pro | 0 .../deploy/android/ui/local.properties | 8 - .../deploy/android/ui/proguard-rules.pro | 21 - .../android/ui/src/main/AndroidManifest.xml | 6 - .../com/baidu/paddle/paddlenlp/ui/Utils.java | 339 -- .../paddlenlp/ui/layout/ActionBarLayout.java | 33 - .../ui/view/AppCompatPreferenceActivity.java | 111 - .../paddlenlp/ui/view/ResultListView.java | 43 - .../ui/view/adapter/BaseResultAdapter.java | 48 - .../ui/view/model/BaseResultModel.java | 41 - .../result_page_border_section_bk.xml | 12 - .../main/res/layout/base_result_page_item.xml | 26 - .../android/ui/src/main/res/values/colors.xml | 22 - .../android/ui/src/main/res/values/styles.xml | 70 - .../ernie-3.0-tiny/deploy/cpp/CMakeLists.txt | 31 - model_zoo/ernie-3.0-tiny/deploy/cpp/README.md | 224 -- .../ernie-3.0-tiny/deploy/cpp/infer_demo.cc | 454 --- .../ernie-3.0-tiny/deploy/python/README.md | 26 +- .../deploy/python/infer_demo.py | 8 +- model_zoo/ernie-3.0/README.md | 39 - model_zoo/ernie-3.0/deploy/cpp/CMakeLists.txt | 35 - model_zoo/ernie-3.0/deploy/cpp/README.md | 272 -- .../ernie-3.0/deploy/cpp/seq_cls_infer.cc | 293 -- .../ernie-3.0/deploy/cpp/token_cls_infer.cc | 320 -- model_zoo/ernie-3.0/deploy/python/README.md | 13 - .../ernie-3.0/deploy/python/seq_cls_infer.py | 8 +- .../deploy/python/token_cls_infer.py | 8 +- model_zoo/ernie-m/deploy/python/README.md | 12 - .../ernie-m/deploy/python/seq_cls_infer.py | 8 +- model_zoo/ernie-vil2.0/deploy/python/infer.py | 6 - model_zoo/uie/README.md | 1 - model_zoo/uie/deploy/python/README.md | 10 - model_zoo/uie/deploy/python/infer.py | 9 +- paddlenlp/transformers/__init__.py | 11 - paddlenlp/transformers/auto/tokenizer.py | 120 +- paddlenlp/transformers/bert/fast_tokenizer.py | 79 - paddlenlp/transformers/configuration_utils.py | 18 - .../transformers/convert_slow_tokenizer.py | 302 -- .../transformers/ernie/fast_tokenizer.py | 78 - .../transformers/ernie_m/fast_tokenizer.py | 135 - .../nystromformer/fast_tokenizer.py | 79 - .../transformers/tinybert/fast_tokenizer.py | 79 - .../transformers/tokenizer_utils_fast.py | 538 ---- requirements-dev.txt | 1 - scripts/regression/ci_case.sh | 61 +- scripts/regression/requirements_ci.txt | 1 - setup.py | 2 +- tests/requirements.txt | 1 - tests/transformers/auto/test_tokenizer.py | 38 +- tests/transformers/bert/test_tokenizer.py | 61 - tests/transformers/ernie/test_tokenizer.py | 56 - .../transformers/ernie_ctm/test_tokenizer.py | 35 - tests/transformers/ernie_m/test_tokenizer.py | 27 +- .../transformers/gau_alpha/test_tokenizer.py | 55 - tests/transformers/layoutlm/test_tokenizer.py | 1 - .../transformers/layoutlmv2/test_tokenizer.py | 1 - .../transformers/layoutxml/test_tokenizer.py | 13 - tests/transformers/nezha/test_tokenizer.py | 54 - .../squeezebert/test_tokenizer.py | 1 - tests/transformers/test_tokenizer_common.py | 62 +- tests/transformers/tinybert/test_tokenizer.py | 55 - tests/transformers/xlm/test_tokenizer.py | 1 - 377 files changed, 53 insertions(+), 35855 deletions(-) delete mode 100644 .github/workflows/fast_tokenizer.yml delete mode 100644 docs/locale/en/LC_MESSAGES/source/paddlenlp.transformers.bert.faster_tokenizer.po delete mode 100644 docs/locale/en/LC_MESSAGES/source/paddlenlp.transformers.ernie.faster_tokenizer.po delete mode 100644 docs/source/paddlenlp.transformers.bert.fast_tokenizer.rst delete mode 100644 docs/source/paddlenlp.transformers.ernie.fast_tokenizer.rst delete mode 100644 docs/source/paddlenlp.transformers.ernie_m.faster_tokenizer.rst delete mode 100644 docs/source/paddlenlp.transformers.tinybert.fast_tokenizer.rst delete mode 100644 fast_tokenizer/CMakeLists.txt delete mode 100644 fast_tokenizer/FastTokenizer.cmake.in delete mode 100644 fast_tokenizer/LICENSE delete mode 100644 fast_tokenizer/Makefile delete mode 100644 fast_tokenizer/README.md delete mode 100644 fast_tokenizer/cmake/ByproductsICU.cmake delete mode 100644 fast_tokenizer/cmake/FindNumPy.cmake delete mode 100644 fast_tokenizer/cmake/dummy.c.in delete mode 100644 fast_tokenizer/cmake/external/dart.cmake delete mode 100644 fast_tokenizer/cmake/external/gflags.cmake delete mode 100644 fast_tokenizer/cmake/external/glog.cmake delete mode 100644 fast_tokenizer/cmake/external/gtest.cmake delete mode 100644 fast_tokenizer/cmake/external/icu.cmake delete mode 100644 fast_tokenizer/cmake/external/nlohmann_json.cmake delete mode 100644 fast_tokenizer/cmake/external/protobuf.cmake delete mode 100644 fast_tokenizer/cmake/external/pybind11.cmake delete mode 100644 fast_tokenizer/cmake/external/python.cmake delete mode 100644 fast_tokenizer/cmake/external/re2.cmake delete mode 100644 fast_tokenizer/cmake/external/utf8proc.cmake delete mode 100644 fast_tokenizer/cmake/generic.cmake delete mode 100644 fast_tokenizer/cmake/python_module.cmake delete mode 100644 fast_tokenizer/cmake/third_party.cmake delete mode 100644 fast_tokenizer/docs/compile/README.md delete mode 100644 fast_tokenizer/docs/compile/how_to_build_android.md delete mode 100644 fast_tokenizer/docs/compile/how_to_build_linux_and_mac.md delete mode 100644 fast_tokenizer/docs/compile/how_to_build_windows.md delete mode 100644 fast_tokenizer/docs/cpp/README.md delete mode 100644 fast_tokenizer/docs/pipeline/README.md delete mode 100644 fast_tokenizer/docs/python/README.md delete mode 100644 fast_tokenizer/examples/clip/README.md delete mode 100644 fast_tokenizer/examples/clip/cpp/CMakeLists.txt delete mode 100644 fast_tokenizer/examples/clip/cpp/README.md delete mode 100644 fast_tokenizer/examples/clip/cpp/demo.cc delete mode 100644 fast_tokenizer/examples/clip/python/README.md delete mode 100644 fast_tokenizer/examples/clip/python/demo.py delete mode 100644 fast_tokenizer/examples/ernie-3.0/README.md delete mode 100644 fast_tokenizer/examples/ernie-3.0/cpp/CMakeLists.txt delete mode 100644 fast_tokenizer/examples/ernie-3.0/cpp/README.md delete mode 100644 fast_tokenizer/examples/ernie-3.0/cpp/demo.cc delete mode 100644 fast_tokenizer/examples/ernie-3.0/python/README.md delete mode 100644 fast_tokenizer/examples/ernie-3.0/python/demo.py delete mode 100644 fast_tokenizer/fast_tokenizer/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/core/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/core/added_vocabulary.cc delete mode 100644 fast_tokenizer/fast_tokenizer/core/added_vocabulary.h delete mode 100644 fast_tokenizer/fast_tokenizer/core/base.cc delete mode 100644 fast_tokenizer/fast_tokenizer/core/base.h delete mode 100644 fast_tokenizer/fast_tokenizer/core/encoding.cc delete mode 100644 fast_tokenizer/fast_tokenizer/core/encoding.h delete mode 100644 fast_tokenizer/fast_tokenizer/core/tokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/core/tokenizer.h delete mode 100644 fast_tokenizer/fast_tokenizer/decoders/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/decoders/decoder.h delete mode 100644 fast_tokenizer/fast_tokenizer/decoders/decoders.h delete mode 100644 fast_tokenizer/fast_tokenizer/decoders/wordpiece.cc delete mode 100644 fast_tokenizer/fast_tokenizer/decoders/wordpiece.h delete mode 100644 fast_tokenizer/fast_tokenizer/models/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/models/bpe.cc delete mode 100644 fast_tokenizer/fast_tokenizer/models/bpe.h delete mode 100644 fast_tokenizer/fast_tokenizer/models/fast_wordpiece.cc delete mode 100644 fast_tokenizer/fast_tokenizer/models/fast_wordpiece.h delete mode 100644 fast_tokenizer/fast_tokenizer/models/model.h delete mode 100644 fast_tokenizer/fast_tokenizer/models/models.h delete mode 100644 fast_tokenizer/fast_tokenizer/models/unigram.cc delete mode 100644 fast_tokenizer/fast_tokenizer/models/unigram.h delete mode 100644 fast_tokenizer/fast_tokenizer/models/wordpiece.cc delete mode 100644 fast_tokenizer/fast_tokenizer/models/wordpiece.h delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/bert.cc delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/bert.h delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/normalizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/normalizer.h delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/normalizers.h delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/precompiled.cc delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/precompiled.h delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/replace.cc delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/replace.h delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/strip.cc delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/strip.h delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/unicode.cc delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/unicode.h delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/utils.cc delete mode 100644 fast_tokenizer/fast_tokenizer/normalizers/utils.h delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/bert.cc delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/bert.h delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/byte_level.cc delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/byte_level.h delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/postprocessor.cc delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/postprocessor.h delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/postprocessors.h delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/roberta.cc delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/roberta.h delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/template.cc delete mode 100644 fast_tokenizer/fast_tokenizer/postprocessors/template.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/bert.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/bert.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/byte_level.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/byte_level.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/metaspace.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/metaspace.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizer.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizers.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/sequence.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/sequence.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/split.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/split.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/whitespace.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/whitespace.h delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/whitespace_and_punctuation.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pretokenizers/whitespace_and_punctuation.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/core.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/core.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/decoders.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/decoders.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/exception.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/exception.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/models.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/models.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/normalizers.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/normalizers.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/postprocessors.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/postprocessors.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/pretokenizers.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/pretokenizers.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/pybind.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/tokenizers.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/tokenizers.h delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/utils.cc delete mode 100644 fast_tokenizer/fast_tokenizer/pybind/utils.h delete mode 100644 fast_tokenizer/fast_tokenizer/test/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/test/gtest_main.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_bert_pretokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_bert_tokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_clip_fast_tokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_ernie_fast_tokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_fast_wordpiece.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_normalizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_replace.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_roberta_postprocessor.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_split_pretokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_strip.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_unicode.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_utils.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_whitespace.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/test_wordpiece.cc delete mode 100644 fast_tokenizer/fast_tokenizer/test/utils.h delete mode 100644 fast_tokenizer/fast_tokenizer/tokenizers/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/tokenizers/clip_fast_tokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/tokenizers/clip_fast_tokenizer.h delete mode 100644 fast_tokenizer/fast_tokenizer/tokenizers/ernie_fast_tokenizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/tokenizers/ernie_fast_tokenizer.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/CMakeLists.txt delete mode 100644 fast_tokenizer/fast_tokenizer/utils/cache.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/failure.cc delete mode 100644 fast_tokenizer/fast_tokenizer/utils/failure.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/lattice.cc delete mode 100644 fast_tokenizer/fast_tokenizer/utils/lattice.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/path.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/sentencepiece_normalizer.cc delete mode 100644 fast_tokenizer/fast_tokenizer/utils/sentencepiece_normalizer.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/shared_mutex.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/string_view.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/trie.cc delete mode 100644 fast_tokenizer/fast_tokenizer/utils/trie.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/unique_ptr.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/utf8.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/utils.cc delete mode 100644 fast_tokenizer/fast_tokenizer/utils/utils.h delete mode 100644 fast_tokenizer/fast_tokenizer/utils/variant.h delete mode 100644 fast_tokenizer/icu_filters.json delete mode 100644 fast_tokenizer/perf/README.md delete mode 100755 fast_tokenizer/perf/perf.py delete mode 100644 fast_tokenizer/perf/requirements.txt delete mode 100644 fast_tokenizer/perf/run_all_perf.sh delete mode 100644 fast_tokenizer/python/CMakeLists.txt delete mode 100644 fast_tokenizer/python/fast_tokenizer/__init__.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/c_wrap.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/decoders/__init__.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/libs/__init__.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/models/__init__.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/normalizers/__init__.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/postprocessors/__init__.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/pretokenizers/__init__.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/tokenizers_impl/__init__.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/tokenizers_impl/base_tokenizer.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/tokenizers_impl/clip.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/tokenizers_impl/ernie.py delete mode 100644 fast_tokenizer/python/fast_tokenizer/tokenizers_impl/sentencepiece_bpe.py delete mode 100644 fast_tokenizer/python/tests/test_byte_level_pretokenizer.py delete mode 100644 fast_tokenizer/python/tests/test_clip_tokenizer.py delete mode 100644 fast_tokenizer/python/tests/test_fast_wordpiece.py delete mode 100644 fast_tokenizer/python/tests/test_tokenizer_json.py delete mode 100644 fast_tokenizer/run_build_android_armv7_lib.sh delete mode 100644 fast_tokenizer/run_build_android_armv7_lite_lib.sh delete mode 100644 fast_tokenizer/run_build_android_armv8_lib.sh delete mode 100644 fast_tokenizer/run_build_android_armv8_lite_lib.sh delete mode 100644 fast_tokenizer/run_build_cpp_lib.bat delete mode 100644 fast_tokenizer/run_build_cpp_lib.sh delete mode 100644 fast_tokenizer/run_build_py_lib.bat delete mode 100644 fast_tokenizer/run_build_py_lib.sh delete mode 100644 fast_tokenizer/run_fast_tokenizer_cpp_test.sh delete mode 100644 fast_tokenizer/setup.py delete mode 100644 fast_tokenizer/tools/codestyle/clang_format.hook delete mode 100644 fast_tokenizer/tools/codestyle/copyright.hook delete mode 100644 fast_tokenizer/tools/codestyle/cpplint_pre_commit.hook delete mode 100644 fast_tokenizer/tools/codestyle/pylint_pre_commit.hook delete mode 100644 model_zoo/ernie-1.0/run_gb512_s1m_static.sh delete mode 100644 model_zoo/ernie-1.0/run_pretrain_static.py delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/.gitignore delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/README.md delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/build.gradle delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/proguard-rules.pro delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/AndroidManifest.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyMainActivity.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinySettingsActivity.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyWelcomeActivity.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-v24/round_corner_btn.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-xhdpi/back_btn.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-xhdpi/more_menu.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings_default.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings_pressed.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/main_bk.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/paddle_logo.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/layout/ernie_tiny_activity_main.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/layout/ernie_tiny_welcome.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/values/arrays.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/values/colors.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/values/dimens.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/values/strings.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/values/styles.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/values/values.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/xml/ernie_tiny_settings.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/build.gradle delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/.gitignore delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/build.gradle delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/consumer-rules.pro delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/libs/.gitignore delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/proguard-rules.pro delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/androidTest/java/com/baidu/paddle/paddlenlp/ernie_tiny/ExampleInstrumentedTest.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/AndroidManifest.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/cpp/CMakeLists.txt delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/cpp/ernie_tiny_jni/convert_jni.h delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/cpp/ernie_tiny_jni/perf_jni.h delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/cpp/ernie_tiny_jni/predictor_jni.cc delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/cpp/ernie_tiny_jni/runtime_option_jni.cc delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/cpp/ernie_tiny_jni/runtime_option_jni.h delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/java/com/baidu/paddle/paddlenlp/ernie_tiny/Initializer.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/java/com/baidu/paddle/paddlenlp/ernie_tiny/IntentDetAndSlotFillResult.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/java/com/baidu/paddle/paddlenlp/ernie_tiny/LitePowerMode.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/java/com/baidu/paddle/paddlenlp/ernie_tiny/Predictor.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/main/java/com/baidu/paddle/paddlenlp/ernie_tiny/RuntimeOption.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ernie_tiny/src/test/java/com/baidu/paddle/paddlenlp/ernie_tiny/ExampleUnitTest.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/gradle.properties delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/gradlew delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/gradlew.bat delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/local.properties delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/settings.gradle delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/.gitignore delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/build.gradle delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/consumer-rules.pro delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/local.properties delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/proguard-rules.pro delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/AndroidManifest.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/java/com/baidu/paddle/paddlenlp/ui/Utils.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/java/com/baidu/paddle/paddlenlp/ui/layout/ActionBarLayout.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/java/com/baidu/paddle/paddlenlp/ui/view/AppCompatPreferenceActivity.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/java/com/baidu/paddle/paddlenlp/ui/view/ResultListView.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/java/com/baidu/paddle/paddlenlp/ui/view/adapter/BaseResultAdapter.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/java/com/baidu/paddle/paddlenlp/ui/view/model/BaseResultModel.java delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/res/drawable-v24/result_page_border_section_bk.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/res/layout/base_result_page_item.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/res/values/colors.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/android/ui/src/main/res/values/styles.xml delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/cpp/CMakeLists.txt delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/cpp/README.md delete mode 100644 model_zoo/ernie-3.0-tiny/deploy/cpp/infer_demo.cc delete mode 100644 model_zoo/ernie-3.0/deploy/cpp/CMakeLists.txt delete mode 100644 model_zoo/ernie-3.0/deploy/cpp/README.md delete mode 100644 model_zoo/ernie-3.0/deploy/cpp/seq_cls_infer.cc delete mode 100644 model_zoo/ernie-3.0/deploy/cpp/token_cls_infer.cc delete mode 100644 paddlenlp/transformers/bert/fast_tokenizer.py delete mode 100644 paddlenlp/transformers/convert_slow_tokenizer.py delete mode 100644 paddlenlp/transformers/ernie/fast_tokenizer.py delete mode 100644 paddlenlp/transformers/ernie_m/fast_tokenizer.py delete mode 100644 paddlenlp/transformers/nystromformer/fast_tokenizer.py delete mode 100644 paddlenlp/transformers/tinybert/fast_tokenizer.py delete mode 100644 paddlenlp/transformers/tokenizer_utils_fast.py diff --git a/.github/workflows/fast_tokenizer.yml b/.github/workflows/fast_tokenizer.yml deleted file mode 100644 index 5eb64d9bdf90..000000000000 --- a/.github/workflows/fast_tokenizer.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: FastTokenizer - -on: - push: - paths: - - 'fast_tokenizer/*' - pull_request: - paths: - - 'fast_tokenizer/*' - -jobs: - fast_tokenizer_cpp: - name: fast_tokenizer_cpp - runs-on: ubuntu-22.04 - permissions: - pull-requests: write - contents: read - id-token: write - steps: - - uses: actions/checkout@v3 - - name: compile - working-directory: ./fast_tokenizer - run: make fast_tokenizer_cpp_compile - - name: test - working-directory: ./fast_tokenizer - run: make fast_tokenizer_cpp_test - fast_tokenizer_python38: - name: fast_tokenizer_python38 - runs-on: ubuntu-22.04 - permissions: - pull-requests: write - contents: read - id-token: write - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v1 - with: - python-version: '3.10' - - name: install - working-directory: ./fast_tokenizer - run: make fast_tokenizer_python_install - - name: compile - working-directory: ./fast_tokenizer - run: make fast_tokenizer_python_compile - - name: test - working-directory: ./fast_tokenizer - run: make fast_tokenizer_python_test - diff --git a/README.md b/README.md index 83764cbfe6db..550cb50fd350 100644 --- a/README.md +++ b/README.md @@ -279,18 +279,6 @@ PaddleNLP针对信息抽取、语义检索、智能问答、情感分析等高 ### 高性能分布式训练与推理 -#### ⚡ FastTokenizer:高性能文本处理库 - -
- -
- -```python -AutoTokenizer.from_pretrained("ernie-3.0-medium-zh", use_fast=True) -``` - -为了实现更极致的模型部署性能,安装FastTokenizer后只需在`AutoTokenizer` API上打开 `use_fast=True`选项,即可调用C++实现的高性能分词算子,轻松获得超Python百余倍的文本处理加速,更多使用说明可参考[FastTokenizer文档](./fast_tokenizer)。 - #### ⚡️ FastGeneration:高性能生成加速库
diff --git a/README_en.md b/README_en.md index e9e4d1405fc2..4801677d17be 100644 --- a/README_en.md +++ b/README_en.md @@ -224,18 +224,6 @@ For more details please refer to [Speech Command Analysis](./applications/speech ### High Performance Distributed Training and Inference -#### ⚡ FastTokenizer: High Performance Text Preprocessing Library - -
- -
- -```python -AutoTokenizer.from_pretrained("ernie-3.0-medium-zh", use_fast=True) -``` - -Set `use_fast=True` to use C++ Tokenizer kernel to achieve 100x faster on text pre-processing. For more usage please refer to [FastTokenizer](./fast_tokenizer). - #### ⚡ FastGeneration: High Performance Generation Library
diff --git a/applications/information_extraction/taskflow_doc.md b/applications/information_extraction/taskflow_doc.md index 538a86e12b21..ef9694d916db 100644 --- a/applications/information_extraction/taskflow_doc.md +++ b/applications/information_extraction/taskflow_doc.md @@ -303,7 +303,6 @@ OCR中识别出来的文字会按照左上到右下进行排序,对于分栏 * `layout_analysis`:是否使用PP-Structure对文档进行布局分析以优化布局信息的排序,默认为False。 * `position_prob`:模型对于span的起始位置/终止位置的结果概率在0~1之间,返回结果去掉小于这个阈值的结果,默认为0.5,span的最终概率输出为起始位置概率和终止位置概率的乘积。 * `precision`:选择模型精度,默认为`fp32`,可选有`fp16`和`fp32`。`fp16`推理速度更快,支持GPU和NPU硬件环境。如果选择`fp16`,在GPU硬件环境下,请先确保机器正确安装NVIDIA相关驱动和基础软件,**确保CUDA>=11.2,cuDNN>=8.1.1**,初次使用需按照提示安装相关依赖。其次,需要确保GPU设备的CUDA计算能力(CUDA Compute Capability)大于7.0,典型的设备包括V100、T4、A10、A100、GTX 20系列和30系列显卡等。更多关于CUDA Compute Capability和精度支持情况请参考NVIDIA文档:[GPU硬件与支持精度对照表](https://docs.nvidia.com/deeplearning/tensorrt/archives/tensorrt-840-ea/support-matrix/index.html#hardware-precision-matrix)。 -* `use_fast`: 使用C++实现的高性能分词算子FastTokenizer进行文本预处理加速。需要通过`pip install fast-tokenizer-python`安装FastTokenizer库后方可使用。默认为`False`。更多使用说明可参考[FastTokenizer文档](../../fast_tokenizer)。 ## References - **[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)** diff --git a/applications/information_extraction/taskflow_doc_en.md b/applications/information_extraction/taskflow_doc_en.md index 09fdc7193073..14f29dc3ab33 100644 --- a/applications/information_extraction/taskflow_doc_en.md +++ b/applications/information_extraction/taskflow_doc_en.md @@ -286,8 +286,7 @@ The text recognized in OCR will be sorted from top left to bottom right. For cas model='uie-x-base', layout_analysis=False, position_prob=0.5, - precision='fp32', - use_fast=False) + precision='fp32') ``` * `schema`: Define the task extraction target, which can be configured by referring to the calling examples of different tasks in the out-of-the-box. @@ -298,7 +297,6 @@ The text recognized in OCR will be sorted from top left to bottom right. For cas * `layout_analysis`: Whether to use PP-Structure to analyze the layout of the document to optimize the sorting of layout information, the default is False. * `position_prob`: The result probability of the model for the start position/end position of the span is between 0 and 1, and the returned result removes the results less than this threshold, the default is 0.5, and the final probability output of the span is the start position probability and end position The product of the position probabilities. * `precision`: select the model precision, the default is `fp32`, optional `fp16` and `fp32`. `fp16` inference is faster, support GPU and NPU hardware. If you choose `fp16` and GPU hardware, please ensure that the machine is correctly installed with NVIDIA-related drivers and basic software. **Ensure that CUDA>=11.2, cuDNN>=8.1.1**. For the first time use, you need to follow the prompts to install the relevant dependencies. Secondly, it is necessary to ensure that the CUDA Compute Capability of the GPU device is greater than 7.0. Typical devices include V100, T4, A10, A100, GTX 20 series and 30 series graphics cards, etc. For more information about CUDA Compute Capability and precision support, please refer to NVIDIA documentation: [GPU Hardware and Supported Precision Comparison Table](https://docs.nvidia.com/deeplearning/tensorrt/archives/tensorrt-840-ea/support-matrix/index.html#hardware-precision-matrix). -* `use_fast`: Use the high-performance word segmentation operator FastTokenizer implemented in C++ to accelerate text preprocessing. The FastTokenizer library needs to be installed through `pip install fast-tokenizer-python` before it can be used. Defaults to `False`. For more usage instructions, please refer to [FastTokenizer Documentation](../../fast_tokenizer). ## References - **[PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)** diff --git a/applications/information_extraction/taskflow_text.md b/applications/information_extraction/taskflow_text.md index a8fe491775be..aa9da3b388e4 100644 --- a/applications/information_extraction/taskflow_text.md +++ b/applications/information_extraction/taskflow_text.md @@ -489,8 +489,7 @@ UIE不限定行业领域和抽取目标,以下是一些通过Taskflow实现开 batch_size=16, model='uie-base', position_prob=0.5, - precision='fp32', - use_fast=False) + precision='fp32') ``` * `schema`:定义任务抽取目标,可参考开箱即用中不同任务的调用示例进行配置。 @@ -499,4 +498,3 @@ UIE不限定行业领域和抽取目标,以下是一些通过Taskflow实现开 * `model`:选择任务使用的模型,默认为`uie-base`,可选有`uie-base`, `uie-medium`, `uie-mini`, `uie-micro`, `uie-nano`和`uie-medical-base`, `uie-base-en`,`uie-x-base`。 * `position_prob`:模型对于span的起始位置/终止位置的结果概率在0~1之间,返回结果去掉小于这个阈值的结果,默认为0.5,span的最终概率输出为起始位置概率和终止位置概率的乘积。 * `precision`:选择模型精度,默认为`fp32`,可选有`fp16`和`fp32`。`fp16`推理速度更快,支持GPU和NPU硬件环境。如果选择`fp16`,在GPU硬件环境下,请先确保机器正确安装NVIDIA相关驱动和基础软件,**确保CUDA>=11.2,cuDNN>=8.1.1**,初次使用需按照提示安装相关依赖。其次,需要确保GPU设备的CUDA计算能力(CUDA Compute Capability)大于7.0,典型的设备包括V100、T4、A10、A100、GTX 20系列和30系列显卡等。更多关于CUDA Compute Capability和精度支持情况请参考NVIDIA文档:[GPU硬件与支持精度对照表](https://docs.nvidia.com/deeplearning/tensorrt/archives/tensorrt-840-ea/support-matrix/index.html#hardware-precision-matrix)。 -* `use_fast`: 使用C++实现的高性能分词算子FastTokenizer进行文本预处理加速。需要通过`pip install fast-tokenizer-python`安装FastTokenizer库后方可使用。默认为`False`。更多使用说明可参考[FastTokenizer文档](../../fast_tokenizer)。 diff --git a/applications/information_extraction/taskflow_text_en.md b/applications/information_extraction/taskflow_text_en.md index d488799313f9..009da922099c 100644 --- a/applications/information_extraction/taskflow_text_en.md +++ b/applications/information_extraction/taskflow_text_en.md @@ -299,8 +299,7 @@ UIE does not limit industry fields and extraction targets. The following are som batch_size=16, model='uie-base', position_prob=0.5, - precision='fp32', - use_fast=False) + precision='fp32') ``` * `schema`: Define the task extraction target, which can be configured by referring to the calling examples of different tasks in the out-of-the-box. @@ -309,4 +308,3 @@ UIE does not limit industry fields and extraction targets. The following are som * `model`: select the model used by the task, the default is `uie-base`, optional `uie-base`, `uie-medium`, `uie-mini`, `uie-micro`, `uie-nano` and `uie-medical-base`, `uie-base-en`, `uie-x-base`. * `position_prob`: The result probability of the model for the start position/end position of the span is between 0 and 1, and the returned result removes the results less than this threshold, the default is 0.5, and the final probability output of the span is the start position probability and end position The product of the position probabilities. * `precision`: select the model precision, the default is `fp32`, optional `fp16` and `fp32`. `fp16` inference is faster, support GPU and NPU hardware. If you choose `fp16` and GPU hardware, please ensure that the machine is correctly installed with NVIDIA-related drivers and basic software. **Ensure that CUDA>=11.2, cuDNN>=8.1.1**. For the first time use, you need to follow the prompts to install the relevant dependencies. Secondly, it is necessary to ensure that the CUDA Compute Capability of the GPU device is greater than 7.0. Typical devices include V100, T4, A10, A100, GTX 20 series and 30 series graphics cards, etc. For more information about CUDA Compute Capability and precision support, please refer to NVIDIA documentation: [GPU Hardware and Supported Precision Comparison Table](https://docs.nvidia.com/deeplearning/tensorrt/archives/tensorrt-840-ea/support-matrix/index.html#hardware-precision-matrix). -* `use_fast`: Use the high-performance word segmentation operator FastTokenizer implemented in C++ to accelerate text preprocessing. The FastTokenizer library needs to be installed through `pip install fast-tokenizer-python` before it can be used. Defaults to `False`. For more usage instructions, please refer to [FastTokenizer Documentation](../../fast_tokenizer). diff --git a/applications/text_classification/hierarchical/deploy/paddle_serving/README.md b/applications/text_classification/hierarchical/deploy/paddle_serving/README.md index 72d65c02db01..dde838a31d34 100644 --- a/applications/text_classification/hierarchical/deploy/paddle_serving/README.md +++ b/applications/text_classification/hierarchical/deploy/paddle_serving/README.md @@ -54,14 +54,6 @@ pip install paddle-serving-server-gpu==0.8.3.post112 -i https://pypi.tuna.tsingh - 默认开启国内清华镜像源来加速下载,如果您使用 HTTP 代理可以关闭(-i https://pypi.tuna.tsinghua.edu.cn/simple) - 更多wheel包请参考[serving官网文档](https://github.com/PaddlePaddle/Serving/blob/develop/doc/Latest_Packages_CN.md) -### 安装FastTokenizer文本处理加速库(可选) -> 重要提示:由于FastTokenizer长时间未得到维护,因此可能会遇到训练(基于Python实现的tokenizer)与部署(基于C++实现的tokenizer)阶段分词不一致的问题。为了确保稳定性和一致性,我们建议避免安装该库。 - -如果想要安装fast_tokenizer,以获得更高的文本处理效率,从而显著提升服务性能。您可以通过以下命令进行安装: -```shell -pip install fast-tokenizer-python -``` - ## 模型转换 @@ -149,7 +141,6 @@ I0727 06:50:34.988327 43126 analysis_predictor.cc:1007] ======= optimize end === I0727 06:50:34.992336 43126 naive_executor.cc:102] --- skip [feed], feed -> token_type_ids I0727 06:50:34.992357 43126 naive_executor.cc:102] --- skip [feed], feed -> input_ids I0727 06:50:34.993671 43126 naive_executor.cc:102] --- skip [linear_75.tmp_1], fetch -> fetch -[2022-07-27 06:50:35,954] [ WARNING] - Can't find the fast_tokenizer package, please ensure install fast_tokenizer correctly. You can install fast_tokenizer by `pip install fast-tokenizer-python`. [2022-07-27 06:50:35,954] [ INFO] - We are using to load 'ernie-3.0-medium-zh'. [2022-07-27 06:50:35,954] [ INFO] - Already cached /root/.paddlenlp/models/ernie-3.0-medium-zh/ernie_3.0_medium_zh_vocab.txt [OP Object] init success diff --git a/applications/text_classification/hierarchical/deploy/predictor/README.md b/applications/text_classification/hierarchical/deploy/predictor/README.md index a043bb3030b7..232293fffb39 100644 --- a/applications/text_classification/hierarchical/deploy/predictor/README.md +++ b/applications/text_classification/hierarchical/deploy/predictor/README.md @@ -19,14 +19,6 @@ python -m pip install onnxruntime-gpu onnx onnxconverter-common==1.9.0 psutil pa python -m pip install onnxruntime psutil ``` -安装FastTokenizer文本处理加速库(可选) -> 重要提示:由于FastTokenizer长时间未得到维护,因此可能会遇到训练(基于Python实现的tokenizer)与部署(基于C++实现的tokenizer)阶段分词不一致的问题。为了确保稳定性和一致性,我们建议避免安装该库。 - -如果想要安装fast_tokenizer,以获得更高的文本处理效率,从而显著提升服务性能。您可以通过以下命令进行安装: -```shell -pip install fast-tokenizer-python -``` - ## 基于GPU部署推理样例 请使用如下命令进行部署 ``` diff --git a/applications/text_classification/hierarchical/deploy/triton_serving/README.md b/applications/text_classification/hierarchical/deploy/triton_serving/README.md index baaef6da68fc..aee42cc32a52 100644 --- a/applications/text_classification/hierarchical/deploy/triton_serving/README.md +++ b/applications/text_classification/hierarchical/deploy/triton_serving/README.md @@ -48,16 +48,6 @@ python3 -m pip install paddlepaddle-gpu paddlenlp -i https://mirror.baidu.com/py 3. 更多关于PaddleNLP安装的详细教程请查看[Installation](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/docs/get_started/installation.rst)。 -### 安装FastTokenizer文本处理加速库(可选) - -> 重要提示:由于FastTokenizer长时间未得到维护,因此可能会遇到训练(基于Python实现的tokenizer)与部署(基于C++实现的tokenizer)阶段分词不一致的问题。为了确保稳定性和一致性,我们建议避免安装该库。 - -如果想要安装fast_tokenizer,以获得更高的文本处理效率,从而显著提升服务性能。您可以通过以下命令进行安装: -```shell -python3 -m pip install fast-tokenizer-python -``` - - ## 模型获取和转换 使用Triton做服务化部署时,选择ONNX Runtime后端运行需要先将模型转换成ONNX格式。 diff --git a/applications/text_classification/multi_class/deploy/triton_serving/README.md b/applications/text_classification/multi_class/deploy/triton_serving/README.md index 9981ba61546f..608999868824 100644 --- a/applications/text_classification/multi_class/deploy/triton_serving/README.md +++ b/applications/text_classification/multi_class/deploy/triton_serving/README.md @@ -48,16 +48,6 @@ python3 -m pip install paddlepaddle-gpu paddlenlp -i https://mirror.baidu.com/py 3. 更多关于PaddleNLP安装的详细教程请查看[Installation](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/docs/get_started/installation.rst)。 -### 安装FastTokenizer文本处理加速库(可选) - -> 重要提示:由于FastTokenizer长时间未得到维护,因此可能会遇到训练(基于Python实现的tokenizer)与部署(基于C++实现的tokenizer)阶段分词不一致的问题。为了确保稳定性和一致性,我们建议避免安装该库。 - -如果想要安装fast_tokenizer,以获得更高的文本处理效率,从而显著提升服务性能。您可以通过以下命令进行安装: -```shell -python3 -m pip install fast-tokenizer-python -``` - - ## 模型获取和转换 使用Triton做服务化部署时,选择ONNX Runtime后端运行需要先将模型转换成ONNX格式。使用Paddle2ONNX将Paddle静态图模型转换为ONNX模型格式的命令如下,以下命令成功运行后,将会在当前目录下生成model.onnx模型文件。 diff --git a/applications/text_classification/multi_label/deploy/paddle_serving/README.md b/applications/text_classification/multi_label/deploy/paddle_serving/README.md index 9587081c1be9..ee19f95492da 100644 --- a/applications/text_classification/multi_label/deploy/paddle_serving/README.md +++ b/applications/text_classification/multi_label/deploy/paddle_serving/README.md @@ -51,14 +51,6 @@ pip install paddle-serving-server-gpu==0.8.3.post112 -i https://pypi.tuna.tsingh - 默认开启国内清华镜像源来加速下载,如果您使用 HTTP 代理可以关闭(-i https://pypi.tuna.tsinghua.edu.cn/simple) - 更多wheel包请参考[serving官网文档](https://github.com/PaddlePaddle/Serving/blob/develop/doc/Latest_Packages_CN.md) -### 安装FastTokenizer文本处理加速库(可选) -> 重要提示:由于FastTokenizer长时间未得到维护,因此可能会遇到训练(基于Python实现的tokenizer)与部署(基于C++实现的tokenizer)阶段分词不一致的问题。为了确保稳定性和一致性,我们建议避免安装该库。 - -如果想要安装fast_tokenizer,以获得更高的文本处理效率,从而显著提升服务性能。您可以通过以下命令进行安装: -```shell -pip install fast-tokenizer-python -``` - ## 模型转换 @@ -144,7 +136,6 @@ I0625 16:44:36.563802 40218 analysis_predictor.cc:1007] ======= optimize end === I0625 16:44:36.571702 40218 naive_executor.cc:102] --- skip [feed], feed -> token_type_ids I0625 16:44:36.571728 40218 naive_executor.cc:102] --- skip [feed], feed -> input_ids I0625 16:44:36.574352 40218 naive_executor.cc:102] --- skip [linear_147.tmp_1], fetch -> fetch -[2022-06-25 16:44:37,545] [ WARNING] - Can't find the fast_tokenizer package, please ensure install fast_tokenizer correctly. You can install fast_tokenizer by `pip install fast-tokenizer-python`. [2022-06-25 16:44:37,546] [ INFO] - We are using to load 'ernie-3.0-medium-zh'. [2022-06-25 16:44:37,546] [ INFO] - Already cached /root/.paddlenlp/models/ernie-3.0-medium-zh/ernie_3.0_base_zh_vocab.txt [OP Object] init success diff --git a/applications/text_classification/multi_label/deploy/predictor/README.md b/applications/text_classification/multi_label/deploy/predictor/README.md index ebc1adce73ff..56786837f16a 100644 --- a/applications/text_classification/multi_label/deploy/predictor/README.md +++ b/applications/text_classification/multi_label/deploy/predictor/README.md @@ -20,13 +20,6 @@ python -m pip install onnxruntime-gpu onnx onnxconverter-common==1.9.0 paddle2on python -m pip install onnxruntime ``` -安装FastTokenizer文本处理加速库(可选) -> 重要提示:由于FastTokenizer长时间未得到维护,因此可能会遇到训练(基于Python实现的tokenizer)与部署(基于C++实现的tokenizer)阶段分词不一致的问题。为了确保稳定性和一致性,我们建议避免安装该库。 - -如果想要安装fast_tokenizer,以获得更高的文本处理效率,从而显著提升服务性能。您可以通过以下命令进行安装: -```shell -pip install fast-tokenizer-python -``` ## 基于GPU部署推理样例 请使用如下命令进行部署 diff --git a/applications/text_classification/multi_label/deploy/triton_serving/README.md b/applications/text_classification/multi_label/deploy/triton_serving/README.md index ce2a5093c57a..8fc05895baf2 100644 --- a/applications/text_classification/multi_label/deploy/triton_serving/README.md +++ b/applications/text_classification/multi_label/deploy/triton_serving/README.md @@ -48,16 +48,6 @@ python3 -m pip install paddlepaddle-gpu paddlenlp -i https://mirror.baidu.com/py 3. 更多关于PaddleNLP安装的详细教程请查看[Installation](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/docs/get_started/installation.rst)。 -### 安装FastTokenizer文本处理加速库(可选) - -> 重要提示:由于FastTokenizer长时间未得到维护,因此可能会遇到训练(基于Python实现的tokenizer)与部署(基于C++实现的tokenizer)阶段分词不一致的问题。为了确保稳定性和一致性,我们建议避免安装该库。 - -如果想要安装fast_tokenizer,以获得更高的文本处理效率,从而显著提升服务性能。您可以通过以下命令进行安装: -```shell -python3 -m pip install fast-tokenizer-python -``` - - ## 模型获取和转换 使用Triton做服务化部署时,选择ONNX Runtime后端运行需要先将模型转换成ONNX格式。 diff --git a/applications/zero_shot_text_classification/deploy/python/README.md b/applications/zero_shot_text_classification/deploy/python/README.md index b1da371d8a7c..20c4371a9f02 100644 --- a/applications/zero_shot_text_classification/deploy/python/README.md +++ b/applications/zero_shot_text_classification/deploy/python/README.md @@ -4,14 +4,6 @@ 本目录下提供 `infer.py` 快速完成在 CPU/GPU 的通用文本分类任务的 Python 部署示例。 -## 依赖安装 - -直接执行以下命令安装部署示例的依赖。 - -```bash -# 安装 fast_tokenizer 以及 GPU 版本 fastdeploy -pip install fast-tokenizer-python fastdeploy-gpu-python -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html -``` ## 快速开始 diff --git a/docs/locale/en/LC_MESSAGES/source/paddlenlp.transformers.bert.faster_tokenizer.po b/docs/locale/en/LC_MESSAGES/source/paddlenlp.transformers.bert.faster_tokenizer.po deleted file mode 100644 index c13b624495d4..000000000000 --- a/docs/locale/en/LC_MESSAGES/source/paddlenlp.transformers.bert.faster_tokenizer.po +++ /dev/null @@ -1,23 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2021, PaddleNLP -# This file is distributed under the same license as the PaddleNLP package. -# FIRST AUTHOR , 2022. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PaddleNLP \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-19 14:17+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.10.1\n" - -#: ../source/paddlenlp.transformers.bert.fast_tokenizer.rst:2 -msgid "fast\\_tokenizer" -msgstr "" - diff --git a/docs/locale/en/LC_MESSAGES/source/paddlenlp.transformers.ernie.faster_tokenizer.po b/docs/locale/en/LC_MESSAGES/source/paddlenlp.transformers.ernie.faster_tokenizer.po deleted file mode 100644 index 92d346421ca1..000000000000 --- a/docs/locale/en/LC_MESSAGES/source/paddlenlp.transformers.ernie.faster_tokenizer.po +++ /dev/null @@ -1,23 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2021, PaddleNLP -# This file is distributed under the same license as the PaddleNLP package. -# FIRST AUTHOR , 2022. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PaddleNLP \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-05-19 14:17+0800\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.10.1\n" - -#: ../source/paddlenlp.transformers.ernie.fast_tokenizer.rst:2 -msgid "fast\\_tokenizer" -msgstr "" - diff --git a/docs/source/paddlenlp.transformers.bert.fast_tokenizer.rst b/docs/source/paddlenlp.transformers.bert.fast_tokenizer.rst deleted file mode 100644 index d102c8899374..000000000000 --- a/docs/source/paddlenlp.transformers.bert.fast_tokenizer.rst +++ /dev/null @@ -1,7 +0,0 @@ -fast\_tokenizer -==================================================== - -.. automodule:: paddlenlp.transformers.bert.fast_tokenizer - :members: - :no-undoc-members: - :show-inheritance: diff --git a/docs/source/paddlenlp.transformers.bert.rst b/docs/source/paddlenlp.transformers.bert.rst index bca1ffa00eca..648d0d6ae5b1 100644 --- a/docs/source/paddlenlp.transformers.bert.rst +++ b/docs/source/paddlenlp.transformers.bert.rst @@ -10,6 +10,5 @@ bert .. toctree:: :maxdepth: 4 - paddlenlp.transformers.bert.fast_tokenizer paddlenlp.transformers.bert.modeling paddlenlp.transformers.bert.tokenizer diff --git a/docs/source/paddlenlp.transformers.ernie.fast_tokenizer.rst b/docs/source/paddlenlp.transformers.ernie.fast_tokenizer.rst deleted file mode 100644 index d076125bafe4..000000000000 --- a/docs/source/paddlenlp.transformers.ernie.fast_tokenizer.rst +++ /dev/null @@ -1,7 +0,0 @@ -fast\_tokenizer -===================================================== - -.. automodule:: paddlenlp.transformers.ernie.fast_tokenizer - :members: - :no-undoc-members: - :show-inheritance: diff --git a/docs/source/paddlenlp.transformers.ernie.rst b/docs/source/paddlenlp.transformers.ernie.rst index 7444d131dbb0..379b0eeb6b68 100644 --- a/docs/source/paddlenlp.transformers.ernie.rst +++ b/docs/source/paddlenlp.transformers.ernie.rst @@ -10,6 +10,5 @@ ernie .. toctree:: :maxdepth: 4 - paddlenlp.transformers.ernie.fast_tokenizer paddlenlp.transformers.ernie.modeling paddlenlp.transformers.ernie.tokenizer diff --git a/docs/source/paddlenlp.transformers.ernie_m.faster_tokenizer.rst b/docs/source/paddlenlp.transformers.ernie_m.faster_tokenizer.rst deleted file mode 100644 index a0c7dcb07bab..000000000000 --- a/docs/source/paddlenlp.transformers.ernie_m.faster_tokenizer.rst +++ /dev/null @@ -1,7 +0,0 @@ -fast\_tokenizer -======================================================== - -.. automodule:: paddlenlp.transformers.ernie_m.fast_tokenizer - :members: - :no-undoc-members: - :show-inheritance: diff --git a/docs/source/paddlenlp.transformers.ernie_m.rst b/docs/source/paddlenlp.transformers.ernie_m.rst index ab97f8f9553a..c65459cda686 100644 --- a/docs/source/paddlenlp.transformers.ernie_m.rst +++ b/docs/source/paddlenlp.transformers.ernie_m.rst @@ -10,6 +10,5 @@ ernie\_m .. toctree:: :maxdepth: 4 - paddlenlp.transformers.ernie_m.fast_tokenizer paddlenlp.transformers.ernie_m.modeling paddlenlp.transformers.ernie_m.tokenizer diff --git a/docs/source/paddlenlp.transformers.tinybert.fast_tokenizer.rst b/docs/source/paddlenlp.transformers.tinybert.fast_tokenizer.rst deleted file mode 100644 index 0f37e8ad03e7..000000000000 --- a/docs/source/paddlenlp.transformers.tinybert.fast_tokenizer.rst +++ /dev/null @@ -1,7 +0,0 @@ -fast\_tokenizer -======================================================== - -.. automodule:: paddlenlp.transformers.tinybert.fast_tokenizer - :members: - :no-undoc-members: - :show-inheritance: diff --git a/docs/source/paddlenlp.transformers.tinybert.rst b/docs/source/paddlenlp.transformers.tinybert.rst index 43b80a253545..e73b38da2d7d 100644 --- a/docs/source/paddlenlp.transformers.tinybert.rst +++ b/docs/source/paddlenlp.transformers.tinybert.rst @@ -10,6 +10,5 @@ tinybert .. toctree:: :maxdepth: 4 - paddlenlp.transformers.tinybert.fast_tokenizer paddlenlp.transformers.tinybert.modeling paddlenlp.transformers.tinybert.tokenizer diff --git a/examples/language_model/roberta/create_data.py b/examples/language_model/roberta/create_data.py index 70b7aee10038..8bd813c47f5c 100644 --- a/examples/language_model/roberta/create_data.py +++ b/examples/language_model/roberta/create_data.py @@ -31,11 +31,6 @@ parser.add_argument( "--dataset_config_name", default="20200501.en", type=str, required=False, help="dataset config name" ) -parser.add_argument( - "--use_slow_tokenizer", - action="store_true", - help="If passed, will use a slow tokenizer (not backed by the 🤗 Tokenizers library).", -) parser.add_argument("--tokenizer_name", default="roberta-base", type=str, required=False, help="tokenizer name") parser.add_argument( "--max_seq_length", @@ -66,7 +61,7 @@ def main(args): # Load pretrained tokenizer if args.tokenizer_name: - tokenizer = AutoTokenizer.from_pretrained(args.tokenizer_name, use_fast=not args.use_slow_tokenizer) + tokenizer = AutoTokenizer.from_pretrained(args.tokenizer_name) # First we tokenize all the texts. column_names = raw_datasets["train"].column_names diff --git a/fast_tokenizer/CMakeLists.txt b/fast_tokenizer/CMakeLists.txt deleted file mode 100644 index ce238239ae7f..000000000000 --- a/fast_tokenizer/CMakeLists.txt +++ /dev/null @@ -1,235 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -cmake_minimum_required(VERSION 3.10) - -project(tokenizers LANGUAGES CXX C VERSION 1.0) - -option(WITH_TESTING "Compile PaddleNLP fast_tokenizer with unit testing" OFF) -option(WITH_PYTHON "Compile PaddleNLP fast_tokenizer with python interpreter" ON) -option(WITH_ICU_LITE "Compile PaddleNLP fast_tokenizer with lite icu library" OFF) -option(USE_ABI0 "Set -D_GLIBCXX_USE_CXX11_ABI to 0" OFF) - -add_definitions(-DFASTTOKENIZER_LIB) - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set (PUBLIC_DEPEND_LIBS "") - -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING - "Choose the type of build, options are: Debug Release -RelWithDebInfo MinSizeRel." - FORCE) -endif(NOT CMAKE_BUILD_TYPE) - -if(NOT WIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11") - if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-as-needed") - endif() - if (USE_ABI0) - message(STATUS "-D_GLIBCXX_USE_CXX11_ABI will be set to 0.") - add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_USE_CXX11_ABI=0") - else() - message(STATUS "-D_GLIBCXX_USE_CXX11_ABI will be set to 1.") - add_definitions(-D_GLIBCXX_USE_CXX11_ABI=1) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_USE_CXX11_ABI=1") - endif() -else() - set(CMAKE_CXX_STANDARD 11) -endif() - -IF(WIN32) -# Need to add flags for windows -foreach( - flag_var - CMAKE_CXX_FLAGS - CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL - CMAKE_CXX_FLAGS_RELWITHDEBINFO - CMAKE_C_FLAGS - CMAKE_C_FLAGS_DEBUG - CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL - CMAKE_C_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - elseif(NOT (${flag_var} MATCHES "/MT")) - set(${flag_var} "${${flag_var}} /MT") - endif() - set(${flag_var} "${${flag_var}}" CACHE STRING "msvc compiler flags" FORCE) -endforeach() - -add_definitions("-DNOMINMAX") -# windows build turn off warnings, use parallel compiling. - -foreach( - flag_var - CMAKE_CXX_FLAGS - CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL - CMAKE_CXX_FLAGS_RELWITHDEBINFO - CMAKE_C_FLAGS - CMAKE_C_FLAGS_DEBUG - CMAKE_C_FLAGS_RELEASE - CMAKE_C_FLAGS_MINSIZEREL - CMAKE_C_FLAGS_RELWITHDEBINFO) - string(REGEX REPLACE "/W[1-4]" " /W0 " ${flag_var} "${${flag_var}}") -endforeach() - -foreach(flag_var CMAKE_CXX_FLAGS CMAKE_C_FLAGS) - set(${flag_var} "${${flag_var}} /w") -endforeach() - -# Windows Remove /Zi, /ZI for Release, MinSizeRel builds -foreach(flag_var - CMAKE_C_FLAGS CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL) -if(${flag_var} MATCHES "/Z[iI]") - string(REGEX REPLACE "/Z[iI]" "" ${flag_var} "${${flag_var}}") -endif() -endforeach() - -set(CMAKE_SUPPRESS_REGENERATION ON) -set(CMAKE_STATIC_LIBRARY_PREFIX lib) - -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /bigobj") -set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /bigobj") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /bigobj") - -if("${CMAKE_GENERATOR}" STREQUAL "Ninja") - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /Zc:inline") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Zc:inline") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zc:inline") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zc:inline") -endif() - -foreach(flag_var CMAKE_SHARED_LINKER_FLAGS CMAKE_STATIC_LINKER_FLAGS - CMAKE_EXE_LINKER_FLAGS CMAKE_LINKER_FLAGS) -set(${flag_var} - "${${flag_var}} /ignore:4049 /ignore:4217 /ignore:4006 /ignore:4221") -set(${flag_var} "${${flag_var}} /NODEFAULTLIB:MSVCRT.LIB") -endforeach() - -ELSE(WIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -fPIC") - IF (NOT APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ldl") - IF (NOT ANDROID) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -lpthread") - ELSE() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os") - ENDIF() - ENDIF() - set (PUBLIC_DEPEND_LIBS ${CMAKE_DL_LIBS}) -ENDIF(WIN32) - -set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) -set(TOKENIZERS_INSTALL_INCLUDE_DIR ${PROJECT_SOURCE_DIR}) - -message("CMAKE_BUILD_TYPE = " ${CMAKE_BUILD_TYPE}) -message("CMAKE_CXX_FLAGS = ${CMAKE_CXX_FLAGS}") -message("CMAKE_EXE_LINKER_FLAGS = ${CMAKE_EXE_LINKER_FLAGS}") - -# config GIT_URL with github mirrors to speed up dependent repos clone -option(GIT_URL "Git URL to clone dependent repos" ${GIT_URL}) -if(NOT GIT_URL) - set(GIT_URL "https://github.com") -endif() - -include_directories(${TOKENIZERS_INSTALL_INCLUDE_DIR}) - -include(generic) -include(third_party) - -add_subdirectory(fast_tokenizer) - -if(WITH_PYTHON) - -add_subdirectory(python) - -if (NOT APPLE AND NOT WIN32) # Linux -add_custom_target(build_tokenizers_bdist_wheel ALL - COMMAND ${PYTHON_EXECUTABLE} setup.py bdist_wheel --plat-name=manylinux1_x86_64 - COMMENT "Packing whl packages------>>>" - DEPENDS copy_python_tokenizers) -else() -add_custom_target(build_tokenizers_bdist_wheel ALL - COMMAND ${CMAKE_COMMAND} -E env ${py_env} ${PYTHON_EXECUTABLE} setup.py bdist_wheel - COMMENT "Packing whl packages------>>>" - DEPENDS copy_python_tokenizers) -endif() - -else(WITH_PYTHON) # Pack fast_tokenizer cpp lib - -set(CPP_PACKAGE_DIR ${CMAKE_BINARY_DIR}/cpp/fast_tokenizer) -add_custom_target(build_cpp_package_dir ALL - COMMAND ${CMAKE_COMMAND} -E make_directory ${CPP_PACKAGE_DIR}/lib ${CPP_PACKAGE_DIR}/include ${CPP_PACKAGE_DIR}/third_party/include ${CPP_PACKAGE_DIR}/third_party/lib - DEPENDS core_tokenizers) - -# copy cmake -configure_file(${PROJECT_SOURCE_DIR}/FastTokenizer.cmake.in ${PROJECT_SOURCE_DIR}/FastTokenizer.cmake @ONLY) -file(COPY ${PROJECT_SOURCE_DIR}/FastTokenizer.cmake DESTINATION ${CPP_PACKAGE_DIR}/) - -# copy headers -file(COPY ${PROJECT_SOURCE_DIR}/fast_tokenizer/ DESTINATION ${CPP_PACKAGE_DIR}/include/fast_tokenizer/ - FILES_MATCHING PATTERN "*.h" - PATTERN "test" EXCLUDE - PATTERN "demo" EXCLUDE - PATTERN "pybind" EXCLUDE) - -add_custom_target(copy_third_party_headers ALL - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${GFLAGS_INCLUDE_DIR} ${ICU_INCLUDE_DIR} ${DART_INCLUDE_DIR} - ${GLOG_INCLUDE_DIR} ${JSON_INCLUDE_DIR} ${RE2_INCLUDE_DIR} - ${CPP_PACKAGE_DIR}/third_party/include - DEPENDS build_cpp_package_dir) - -# copy library -set(TOKENIZER_CORE_NAME "core_tokenizers") -set(TOKENIZER_CORE_PATH ${CMAKE_BINARY_DIR}/fast_tokenizer) -if (WIN32) - set(ICU_DLL_DIR ${CMAKE_BINARY_DIR}/third_party/icu/src/extern_icu/icu4c/bin64) - set(ICU_LIB_DIR ${CMAKE_BINARY_DIR}/third_party/icu/src/extern_icu/icu4c/lib64) - add_custom_target(copy_shared_library ALL - COMMAND ${CMAKE_COMMAND} -E copy ${TOKENIZER_CORE_PATH}/${TOKENIZER_CORE_NAME}.dll ${TOKENIZER_CORE_PATH}/${TOKENIZER_CORE_NAME}.lib ${CPP_PACKAGE_DIR}/lib - COMMAND ${CMAKE_COMMAND} -E copy ${ICU_DLL_DIR}/icudt70.dll ${ICU_DLL_DIR}/icuuc70.dll ${ICU_LIB_DIR}/icudt.lib ${ICU_LIB_DIR}/icuuc.lib ${CPP_PACKAGE_DIR}/third_party/lib - DEPENDS build_cpp_package_dir core_tokenizers) -elseif(APPLE) - set(TOKENIZER_CORE_LIBS_PATH "${TOKENIZER_CORE_PATH}/lib${TOKENIZER_CORE_NAME}.dylib") - add_custom_target(copy_shared_library ALL - COMMAND ${CMAKE_COMMAND} -E copy ${TOKENIZER_CORE_LIBS_PATH} ${CPP_PACKAGE_DIR}/lib - DEPENDS build_cpp_package_dir core_tokenizers) -elseif(ANDROID) - set(TOKENIZER_CORE_LIBS_PATH "${TOKENIZER_CORE_PATH}/lib${TOKENIZER_CORE_NAME}.so") - add_custom_target(copy_shared_library ALL - COMMAND ${CMAKE_COMMAND} -E copy ${TOKENIZER_CORE_LIBS_PATH} ${CPP_PACKAGE_DIR}/lib - COMMAND ${CMAKE_STRIP} --strip-all ${CPP_PACKAGE_DIR}/lib/lib${TOKENIZER_CORE_NAME}.so - DEPENDS build_cpp_package_dir core_tokenizers) -else() - set(TOKENIZER_CORE_LIBS_PATH "${TOKENIZER_CORE_PATH}/lib${TOKENIZER_CORE_NAME}.so") - add_custom_target(copy_shared_library ALL - COMMAND ${CMAKE_COMMAND} -E copy ${TOKENIZER_CORE_LIBS_PATH} ${CPP_PACKAGE_DIR}/lib - DEPENDS build_cpp_package_dir core_tokenizers) -endif() - -add_custom_target(create_commit_id_file ALL - COMMAND ${GIT_EXECUTABLE} log -1 --format=%H > ${CPP_PACKAGE_DIR}/commit.log - DEPENDS copy_shared_library) -endif(WITH_PYTHON) - diff --git a/fast_tokenizer/FastTokenizer.cmake.in b/fast_tokenizer/FastTokenizer.cmake.in deleted file mode 100644 index cf9c34186768..000000000000 --- a/fast_tokenizer/FastTokenizer.cmake.in +++ /dev/null @@ -1,44 +0,0 @@ -CMAKE_MINIMUM_REQUIRED (VERSION 3.12) - -set(USE_ABI0 @USE_ABI0@) - -if (NOT WIN32) - if (USE_ABI0) - add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) - else() - add_definitions(-D_GLIBCXX_USE_CXX11_ABI=1) - endif() -endif() - - -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE "Release" CACHE STRING - "Choose the type of build, options are: Debug Release -RelWithDebInfo MinSizeRel." - FORCE) -endif(NOT CMAKE_BUILD_TYPE) - -if(NOT WIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++11") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -Wno-narrowing") -else() - set(CMAKE_CXX_STANDARD 11) -endif() - -set(LIBRARY_NAME core_tokenizers) - -set(FAST_TOKENIZER_INCS "") -list(APPEND FAST_TOKENIZER_INCS ${CMAKE_CURRENT_LIST_DIR}/include) -list(APPEND FAST_TOKENIZER_INCS ${CMAKE_CURRENT_LIST_DIR}/third_party/include) - -set(FAST_TOKENIZER_LIBS "") -find_library(FTLIB ${LIBRARY_NAME} ${CMAKE_CURRENT_LIST_DIR}/lib NO_DEFAULT_PATH) -list(APPEND FAST_TOKENIZER_LIBS ${FTLIB}) - -if (WIN32) -find_library(ICUDT icudt ${CMAKE_CURRENT_LIST_DIR}/third_party/lib NO_DEFAULT_PATH) -list(APPEND FAST_TOKENIZER_LIBS ${ICUDT}) - -find_library(ICUUC icuuc ${CMAKE_CURRENT_LIST_DIR}/third_party/lib NO_DEFAULT_PATH) -list(APPEND FAST_TOKENIZER_LIBS ${ICUUC}) -endif() diff --git a/fast_tokenizer/LICENSE b/fast_tokenizer/LICENSE deleted file mode 100644 index d945753f93c1..000000000000 --- a/fast_tokenizer/LICENSE +++ /dev/null @@ -1,203 +0,0 @@ -Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/fast_tokenizer/Makefile b/fast_tokenizer/Makefile deleted file mode 100644 index 4823a02a3cc1..000000000000 --- a/fast_tokenizer/Makefile +++ /dev/null @@ -1,38 +0,0 @@ -# Makefile for fast_tokenizer -# -# GitHb: https://github.com/PaddlePaddle/PaddleNLP -# Author: Paddle Team https://github.com/PaddlePaddle -# - -# Compile and test for fast_tokenizer cpp library - -.PHONY: fast_tokenizer_cpp_compile - -fast_tokenizer_cpp_compile: - mkdir -p build_cpp && cd build_cpp && \ - cmake .. -DWITH_PYTHON=OFF -DWITH_TESTING=ON -DCMAKE_BUILD_TYPE=Release && \ - make -j4 - -.PHONY: fast_tokenizer_cpp_test - -fast_tokenizer_cpp_test: - bash run_fast_tokenizer_cpp_test.sh build_cpp/fast_tokenizer/test - -# Compile and test for fast_tokenizer python library - -.PHONY: fast_tokenizer_python_install - -fast_tokenizer_python_install: - pip install numpy wheel pytest paddlepaddle .. - -.PHONY: fast_tokenizer_python_compile - -fast_tokenizer_python_compile: - mkdir -p build_py && cd build_py && \ - cmake .. -DWITH_PYTHON=ON -DWITH_TESTING=OFF -DCMAKE_BUILD_TYPE=Release && \ - make -j4 - -.PHONY: fast_tokenizer_python_test - -fast_tokenizer_python_test: - pip install build_py/dist/*whl && pytest build_py/python/tests diff --git a/fast_tokenizer/README.md b/fast_tokenizer/README.md deleted file mode 100644 index 6b8d18e6f56f..000000000000 --- a/fast_tokenizer/README.md +++ /dev/null @@ -1,150 +0,0 @@ - -# ⚡ FastTokenizer:高性能文本处理库 - ------------------------------------------------------------------------------------------- - -

- - - - - - - - - -

- - -FastTokenizer 是一款简单易用、功能强大的跨平台高性能文本预处理库,集成业界多个常用的 Tokenizer 实现,支持不同 NLP 场景下的文本预处理功能,如文本分类、阅读理解,序列标注等。在 Python 端结合 PaddleNLP Tokenizer 模块,为用户在训练、推理阶段提供高效通用的文本预处理能力。 - -![fast-tokenizer-framework](https://user-images.githubusercontent.com/10826371/215277623-1fcd84e5-1cc7-43a8-a33c-890103cf1cc8.png) - -## 特性 - -- 高性能。底层采用 C++ 实现,并且集成高性能分词算法 `FastWordPiece` [1],其性能远高于常规 Python 实现的 Tokenizer。在文本分类任务上,FastTokenizer 对比 Python 版本 Tokenizer 加速比最高可达20倍。支持多线程加速多文本批处理分词。默认使用单线程分词。 -- 可拓展性强。用户可以通过指定不同的 `Normalizer`, `PreTokenizer`, `Model` 以及 `PostProcessor` 组件自定义 Tokenizer。可在 [FastTokenizer Pipeline](docs/pipeline/README.md) 文档了解更多关于组件的介绍以及使用方式。 -- 跨平台。FastTokenizer 可在不同的系统平台上使用,目前已支持 Windows x64,Linux x64 以及 MacOS 10.14+ 平台上使用。 -- 支持多编程语言。FastTokenizer 提供在 [C++](./docs/cpp/README.md)、[Python](./docs/python/README.md) 语言上开发的能力。 -- 包含所有预处理。覆盖绝大部分 Transformer 模型的 Tokenizer 所需要的功能,包括特殊 Tokens 的拼接、截断等。输入的原始文本经过 FastTokenizer 处理后得到的结果可直接输入到 Transformer 类模型。 - -## 快速开始 - -下面将介绍 Python 版本 FastTokenizer 的使用方式,C++ 版本的使用方式可参考 [FastTokenizer C++ 库使用教程](./docs/cpp/README.md)。 - -### 环境依赖 - -- Windows 64位系统 -- Linux x64系统 -- MacOS 10.14+系统( m1 芯片的 MacOS,需要使用 x86_64 版本的 Anaconda 作为 python 环境方可安装使用) -- Python 3.6 ~ 3.10 - -### 安装 FastTokenizer - -```python -pip install fast-tokenizer-python -``` - -### FastTokenizer 使用示例 - -- 准备词表 - -```shell -# Linux或者Mac用户可直接执行以下命令下载测试的词表,Windows 用户可在浏览器上下载到本地。 -wget https://bj.bcebos.com/paddlenlp/models/transformers/ernie/vocab.txt -``` - -- 切词示例 - -FastTokenizer 库内置 NLP 任务常用的 Tokenizer,如 ErnieFastTokenizer。下面将展示 FastTokenizer 的简单用法。 - -```python -import fast_tokenizer -from fast_tokenizer import ErnieFastTokenizer, models - -# 0.(可选)设置线程数 -fast_tokenizer.set_thread_num(1) -# 1. 加载词表 -vocab = models.WordPiece.read_file("ernie_vocab.txt") -# 2. 实例化 ErnieFastTokenizer 对象 -fast_tokenizer = ErnieFastTokenizer(vocab) -# 3. 切词 -output = fast_tokenizer.encode("我爱中国") -# 4. 输出结果 -print("ids: ", output.ids) -print("type_ids: ", output.type_ids) -print("tokens: ", output.tokens) -print("offsets: ", output.offsets) -print("attention_mask: ", output.attention_mask) - -# 5. 示例输出 -# ids: [1, 75, 329, 12, 20, 2] -# type_ids: [0, 0, 0, 0, 0, 0] -# tokens: ['[CLS]', '我', '爱', '中', '国', '[SEP]'] -# offsets: [(0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (0, 0)] -# attention_mask: [1, 1, 1, 1, 1, 1] -``` - -### FastTokenizer 在 PaddleNLP Tokenizer 模块加速示例 - -PaddleNLP Tokenizer 模块可简单地应用在模型训练以及推理部署的文本预处理阶段,并通过 `AutoTokenizer.from_pretrained` 方式实例化相应的 Tokenizer 。其中 `AutoTokenizer` 默认加载得到的 Tokenizer 是常规 Python 实现的 Tokenizer,其性能会低于 C++ 实现的 FastTokenizer。为了提升 PaddleNLP Tokenizer 模块性能,目前 PaddleNLP Tokenizer 模块已经支持使用 FastTokenizer 作为 Tokenizer 的后端加速切词阶段。在现有的 Tokenizer 加载接口中,仅需添加 `use_fast=True` 这一关键词参数,其余代码保持不变,即可加载 Fast 版本的 Tokenizer,代码示例如下: - -```python -from paddlenlp.transformers import AutoTokenizer - -# 默认加载Python版本的Tokenizer -tokenizer = AutoTokenizer.from_pretrained('ernie-3.0-medium-zh') -# 打开use_fast开关,可加载Fast版本Tokenizer -fast_tokenizer = AutoTokenizer.from_pretrained('ernie-3.0-medium-zh', use_fast=True) - -text1 = tokenizer('自然语言处理') -text2 = fast_tokenizer('自然语言处理') - -print(text1) -print(text2) - -# 示例输出 -# {'input_ids': [1, 67, 187, 405, 545, 239, 38, 2], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0]} -# {'input_ids': [1, 67, 187, 405, 545, 239, 38, 2], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0]} - -``` - -目前 PaddleNLP 已支持 BERT、ERNIE、TinyBERT 以及 ERNIE-M 4种 Tokenizer 的 Fast 版本,其余模型的 Tokenizer 暂不支持 Fast 版本。 - -## FAQ - -**Q:我在 AutoTokenizer.from_pretrained 接口上已经打开 `use_fast=True` 开关,为什么文本预处理阶段性能上好像没有任何变化?** - -A:在有三种情况下,打开 `use_fast=True` 开关可能无法提升性能: - 1. 没有安装 fast_tokenizer 。若在没有安装 fast_tokenizer 库的情况下打开 `use_fast` 开关,PaddleNLP 会给出以下warning:"Can't find the fast_tokenizer package, please ensure install fast_tokenizer correctly. "。 - - 2. 加载的 Tokenizer 类型暂不支持 Fast 版本。目前支持4种 Tokenizer 的 Fast 版本,分别是 BERT、ERNIE、TinyBERT 以及 ERNIE-M Tokenizer。若加载不支持 Fast 版本的 Tokenizer 情况下打开 `use_fast` 开关,PaddleNLP 会给出以下warning:"The tokenizer XXX doesn't have the fast version. Please check the map paddlenlp.transformers.auto.tokenizer.FAST_TOKENIZER_MAPPING_NAMES to see which fast tokenizers are currently supported." - - 3. 待切词文本长度过短(如文本平均长度小于5)。这种情况下切词开销可能不是整个文本预处理的性能瓶颈,导致在使用 FastTokenizer 后仍无法提升整体性能。 - -**Q:如何使用多线程加速分词?** - -A:可以通过调用 `fast_tokenizer.set_thread_num(xxx)` 使用多线程进行分词。需要谨慎开启多线程加速分词,在以下场景下可以考虑开启多线程: - 1. CPU资源充足。若在推理阶段使用CPU进行推理,开启多线程分词可能会出现资源竞争情况,从而影响推理阶段的性能。 - - 2. 文本的批大小较大。若批大小比较小,开启多线程可能不会得到任何加速效果,并且可能会因为线程调度导致延时增长。建议批大小大于4的时候再考虑开启多线程分词。 - - 3. 文本长度较长。若文本长度较短,开启多线程可能不会得到任何加速效果,并且可能会因为线程调度导致延时增长。建议文本平均长度大于16的时候再考虑开启多线程分词。 - -**Q:Windows 上编译、运行示例出错。** 相关issue:[issues 4673](https://github.com/PaddlePaddle/PaddleNLP/issues/4673)。 - -A:FastTokenizer 支持 Linux、Windows 以及 MacOS 系统上运行,同一示例可以在不同的操作系统上运行。如果出现在其他系统编译运行没错,但在 Windows 上编译或者运行示例出错的问题,大概率是编译过程中遇到中文字符的编码问题,FastTokenizer 要求字符集必须为 UTF-8。可以参考Visual Studio的官方文档,设置源字符集为/utf-8解决:[/utf-8(将源字符集和执行字符集设置为 UTF-8)](https://learn.microsoft.com/zh-cn/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170)。 - -## 参考文献 - -- [1] Xinying Song, Alex Salcianuet al. "Fast WordPiece Tokenization", EMNLP, 2021 - -## 相关文档 - -[FastTokenizer Pipeline](docs/pipeline/README.md) - -[FastTokenizer 编译指南](docs/compile/README.md) - -[FastTokenizer C++ 库使用教程](./docs/cpp/README.md) - -[FastTokenizer Python 库使用教程](./docs/python/README.md) diff --git a/fast_tokenizer/cmake/ByproductsICU.cmake b/fast_tokenizer/cmake/ByproductsICU.cmake deleted file mode 100644 index 3b68f08249bc..000000000000 --- a/fast_tokenizer/cmake/ByproductsICU.cmake +++ /dev/null @@ -1,54 +0,0 @@ -# MIT License -# -# Copyright (c) 2018 The ViaDuck Project -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -function(GetICUByproducts ICU_PATH ICU_LIB_VAR ICU_INCLUDE_VAR ICU_BASE_NAMES_VAR) - # include directory - set(${ICU_INCLUDE_VAR} "${ICU_PATH}/include" PARENT_SCOPE) - - if (WIN32) - # windows basenames and pre/suffixes - set(ICU_LIB_BASE_NAMES dt in io tu uc) - - set(ICU_SHARED_PREFIX "lib") - set(ICU_STATIC_PREFIX "") - set(ICU_SHARED_SUFFIX ".dll.a") - set(ICU_STATIC_SUFFIX ".lib") - set(ICU_INSTALL_LIB "lib64") - else() - # unix basenames and pre/suffixes - set(ICU_LIB_BASE_NAMES i18n data uc io tu) - set(ICU_SHARED_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX}) - set(ICU_STATIC_PREFIX ${CMAKE_STATIC_LIBRARY_PREFIX}) - set(ICU_SHARED_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(ICU_STATIC_SUFFIX ${CMAKE_STATIC_LIBRARY_SUFFIX}) - set(ICU_INSTALL_LIB "lib") - endif() - # add static and shared libs to the libraries variable - foreach(ICU_BASE_NAME ${ICU_LIB_BASE_NAMES}) - set(ICU_SHARED_LIB "${ICU_PATH}/${ICU_INSTALL_LIB}/${ICU_SHARED_PREFIX}icu${ICU_BASE_NAME}${ICU_SHARED_SUFFIX}") - set(ICU_STATIC_LIB "${ICU_PATH}/${ICU_INSTALL_LIB}/${ICU_STATIC_PREFIX}icu${ICU_BASE_NAME}${ICU_STATIC_SUFFIX}") - - if (ICU_STATIC) - list(APPEND ${ICU_LIB_VAR} ${ICU_STATIC_LIB}) - else() - list(APPEND ${ICU_LIB_VAR} ${ICU_SHARED_LIB}) - endif() - list(APPEND ${ICU_BASE_NAMES_VAR} ${ICU_BASE_NAME}) - endforeach() - set(${ICU_LIB_VAR} ${${ICU_LIB_VAR}} PARENT_SCOPE) - set(${ICU_BASE_NAMES_VAR} ${${ICU_BASE_NAMES_VAR}} PARENT_SCOPE) -endfunction() \ No newline at end of file diff --git a/fast_tokenizer/cmake/FindNumPy.cmake b/fast_tokenizer/cmake/FindNumPy.cmake deleted file mode 100644 index 7815e86cb503..000000000000 --- a/fast_tokenizer/cmake/FindNumPy.cmake +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Find the Python NumPy package -# PYTHON_NUMPY_INCLUDE_DIR -# NUMPY_FOUND -# will be set by this script - -cmake_minimum_required(VERSION 2.6) - -if(NOT PYTHON_EXECUTABLE) - if(NumPy_FIND_QUIETLY) - find_package(PythonInterp QUIET) - else() - find_package(PythonInterp) - set(_numpy_out 1) - endif() -endif() - -if (PYTHON_EXECUTABLE) - # write a python script that finds the numpy path - file(WRITE ${PROJECT_BINARY_DIR}/FindNumpyPath.py - "try: import numpy; print(numpy.get_include())\nexcept:pass\n") - - # execute the find script - exec_program("${PYTHON_EXECUTABLE}" ${PROJECT_BINARY_DIR} - ARGS "FindNumpyPath.py" - OUTPUT_VARIABLE NUMPY_PATH) -elseif(_numpy_out) - message(STATUS "Python executable not found.") -endif(PYTHON_EXECUTABLE) - -find_path(PYTHON_NUMPY_INCLUDE_DIR numpy/arrayobject.h - HINTS "${NUMPY_PATH}" "${PYTHON_INCLUDE_PATH}") - -if(PYTHON_NUMPY_INCLUDE_DIR) - set(PYTHON_NUMPY_FOUND 1 CACHE INTERNAL "Python numpy found") -endif(PYTHON_NUMPY_INCLUDE_DIR) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(NumPy DEFAULT_MSG PYTHON_NUMPY_INCLUDE_DIR) diff --git a/fast_tokenizer/cmake/dummy.c.in b/fast_tokenizer/cmake/dummy.c.in deleted file mode 100644 index 17ba4d3495eb..000000000000 --- a/fast_tokenizer/cmake/dummy.c.in +++ /dev/null @@ -1,3 +0,0 @@ -// Generated by @dummy_GENERATOR@. DO NOT EDIT!!! - -const char *dummy = "@dummy_CONTENT@"; \ No newline at end of file diff --git a/fast_tokenizer/cmake/external/dart.cmake b/fast_tokenizer/cmake/external/dart.cmake deleted file mode 100644 index 1e00807f7745..000000000000 --- a/fast_tokenizer/cmake/external/dart.cmake +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -include(ExternalProject) - -set(DART_PREFIX_DIR ${THIRD_PARTY_PATH}/dart) -SET(DART_REPOSITORY ${GIT_URL}/s-yata/darts-clone.git) -SET(DART_TAG master) - -set(DART_INCLUDE_DIR ${THIRD_PARTY_PATH}/dart/src/extern_dart/include) -include_directories(${DART_INCLUDE_DIR}) - -ExternalProject_Add( - extern_dart - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${DART_REPOSITORY} - GIT_TAG ${DART_TAG} - PREFIX ${DART_PREFIX_DIR} - # If we explicitly leave the `UPDATE_COMMAND` of the ExternalProject_Add - # function in CMakeLists blank, it will cause another parameter GIT_TAG - # to be modified without triggering incremental compilation, and the - # third-party library version changes cannot be incorporated. - # reference: https://cmake.org/cmake/help/latest/module/ExternalProject.html - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) - -add_library(dart INTERFACE) - -add_dependencies(dart extern_dart) diff --git a/fast_tokenizer/cmake/external/gflags.cmake b/fast_tokenizer/cmake/external/gflags.cmake deleted file mode 100644 index cb7e0420045a..000000000000 --- a/fast_tokenizer/cmake/external/gflags.cmake +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -INCLUDE(ExternalProject) - -SET(GFLAGS_PREFIX_DIR ${THIRD_PARTY_PATH}/gflags) -SET(GFLAGS_INSTALL_DIR ${THIRD_PARTY_PATH}/install/gflags) -SET(GFLAGS_INCLUDE_DIR "${GFLAGS_INSTALL_DIR}/include" CACHE PATH "gflags include directory." FORCE) -set(GFLAGS_REPOSITORY ${GIT_URL}/gflags/gflags.git) -set(GFLAGS_TAG "v2.2.2") -IF(WIN32) - set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/gflags_static.lib" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE) -ELSE(WIN32) - set(GFLAGS_LIBRARIES "${GFLAGS_INSTALL_DIR}/lib/libgflags.a" CACHE FILEPATH "GFLAGS_LIBRARIES" FORCE) - set(BUILD_COMMAND $(MAKE) --silent) - set(INSTALL_COMMAND $(MAKE) install) -ENDIF(WIN32) - -INCLUDE_DIRECTORIES(${GFLAGS_INCLUDE_DIR}) - -IF(ANDROID) -set(CROSS_COMPILE_CMAKE_ARGS - "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}" - "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}" - "-DCMAKE_ANDROID_ARCH_ABI=${CMAKE_ANDROID_ARCH_ABI}" - "-DCMAKE_ANDROID_NDK=${CMAKE_ANDROID_NDK}" - "-DCMAKE_ANDROID_STL_TYPE=${CMAKE_ANDROID_STL_TYPE}" - "-DANDROID_ABI=${CMAKE_ANDROID_ARCH_ABI}" - "-DANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN}" - "-DANDROID_STL=${CMAKE_ANDROID_STL_TYPE}" - "-DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}" - "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_ANDROID_NDK}/build/cmake/android.toolchain.cmake" - "-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=${CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION}" - "-DANDROID_PLATFORM=android-${ANDROID_NATIVE_API_LEVEL}" - "-D__ANDROID_API__=${ANDROID_NATIVE_API_LEVEL}") - -ExternalProject_Add( - extern_gflags - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${GFLAGS_REPOSITORY} - GIT_TAG ${GFLAGS_TAG} - PREFIX ${GFLAGS_PREFIX_DIR} - UPDATE_COMMAND "" - BUILD_COMMAND ${BUILD_COMMAND} - INSTALL_COMMAND ${INSTALL_COMMAND} - CMAKE_ARGS ${CROSS_COMPILE_CMAKE_ARGS} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} - -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} - -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} - -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} - -DBUILD_STATIC_LIBS=ON - -DCMAKE_INSTALL_PREFIX=${GFLAGS_INSTALL_DIR} - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DBUILD_TESTING=OFF - -DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE} - ${EXTERNAL_OPTIONAL_ARGS} - CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GFLAGS_INSTALL_DIR} - -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - BUILD_BYPRODUCTS ${GFLAGS_LIBRARIES} -) -ELSE() -ExternalProject_Add( - extern_gflags - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${GFLAGS_REPOSITORY} - GIT_TAG ${GFLAGS_TAG} - PREFIX ${GFLAGS_PREFIX_DIR} - UPDATE_COMMAND "" - BUILD_COMMAND ${BUILD_COMMAND} - INSTALL_COMMAND ${INSTALL_COMMAND} - CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} - -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} - -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} - -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} - -DBUILD_STATIC_LIBS=ON - -DCMAKE_INSTALL_PREFIX=${GFLAGS_INSTALL_DIR} - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DBUILD_TESTING=OFF - -DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE} - ${EXTERNAL_OPTIONAL_ARGS} - CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GFLAGS_INSTALL_DIR} - -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - BUILD_BYPRODUCTS ${GFLAGS_LIBRARIES} -) -ENDIF() - -ADD_LIBRARY(gflags STATIC IMPORTED GLOBAL) -SET_PROPERTY(TARGET gflags PROPERTY IMPORTED_LOCATION ${GFLAGS_LIBRARIES}) -ADD_DEPENDENCIES(gflags extern_gflags) - -# On Windows (including MinGW), the Shlwapi library is used by gflags if available. -if (WIN32) - include(CheckIncludeFileCXX) - check_include_file_cxx("shlwapi.h" HAVE_SHLWAPI) - if (HAVE_SHLWAPI) - set_property(GLOBAL PROPERTY OS_DEPENDENCY_MODULES shlwapi.lib) - endif(HAVE_SHLWAPI) -endif (WIN32) \ No newline at end of file diff --git a/fast_tokenizer/cmake/external/glog.cmake b/fast_tokenizer/cmake/external/glog.cmake deleted file mode 100644 index 2afc39608ce1..000000000000 --- a/fast_tokenizer/cmake/external/glog.cmake +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -INCLUDE(ExternalProject) - -SET(GLOG_PREFIX_DIR ${THIRD_PARTY_PATH}/glog) -SET(GLOG_INSTALL_DIR ${THIRD_PARTY_PATH}/install/glog) -SET(GLOG_INCLUDE_DIR "${GLOG_INSTALL_DIR}/include" CACHE PATH "glog include directory." FORCE) -SET(GLOG_REPOSITORY ${GIT_URL}/google/glog.git) -SET(GLOG_TAG v0.4.0) - -IF(WIN32) - SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/glog.lib" CACHE FILEPATH "glog library." FORCE) - SET(GLOG_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267 /wd4530") - add_definitions("/DGOOGLE_GLOG_DLL_DECL=") -ELSE(WIN32) - SET(GLOG_LIBRARIES "${GLOG_INSTALL_DIR}/lib/libglog.a" CACHE FILEPATH "glog library." FORCE) - SET(GLOG_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) -ENDIF(WIN32) - -INCLUDE_DIRECTORIES(${GLOG_INCLUDE_DIR}) - -IF(ANDROID) -set(CROSS_COMPILE_CMAKE_ARGS - "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}" - "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}" - "-DCMAKE_ANDROID_ARCH_ABI=${CMAKE_ANDROID_ARCH_ABI}" - "-DCMAKE_ANDROID_NDK=${CMAKE_ANDROID_NDK}" - "-DCMAKE_ANDROID_STL_TYPE=${CMAKE_ANDROID_STL_TYPE}" - "-DANDROID_ABI=${CMAKE_ANDROID_ARCH_ABI}" - "-DANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN}" - "-DANDROID_STL=${CMAKE_ANDROID_STL_TYPE}" - "-DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}" - "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_ANDROID_NDK}/build/cmake/android.toolchain.cmake" - "-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=${CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION}" - "-DANDROID_PLATFORM=android-${ANDROID_NATIVE_API_LEVEL}" - "-D__ANDROID_API__=${ANDROID_NATIVE_API_LEVEL}") - -ExternalProject_Add( - extern_glog - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${GLOG_REPOSITORY} - GIT_TAG ${GLOG_TAG} - DEPENDS gflags - PREFIX ${GLOG_PREFIX_DIR} - UPDATE_COMMAND "" - CMAKE_ARGS ${CROSS_COMPILE_CMAKE_ARGS} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_FLAGS=${GLOG_CMAKE_CXX_FLAGS} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} - -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} - -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} - -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} - -DCMAKE_INSTALL_PREFIX=${GLOG_INSTALL_DIR} - -DCMAKE_INSTALL_LIBDIR=${GLOG_INSTALL_DIR}/lib - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DWITH_GFLAGS=OFF - -DBUILD_TESTING=OFF - -DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE} - ${EXTERNAL_OPTIONAL_ARGS} - CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GLOG_INSTALL_DIR} - -DCMAKE_INSTALL_LIBDIR:PATH=${GLOG_INSTALL_DIR}/lib - -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - BUILD_BYPRODUCTS ${GLOG_LIBRARIES} -) -ELSE() -ExternalProject_Add( - extern_glog - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${GLOG_REPOSITORY} - GIT_TAG ${GLOG_TAG} - DEPENDS gflags - PREFIX ${GLOG_PREFIX_DIR} - UPDATE_COMMAND "" - CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_FLAGS=${GLOG_CMAKE_CXX_FLAGS} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} - -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} - -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} - -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} - -DCMAKE_INSTALL_PREFIX=${GLOG_INSTALL_DIR} - -DCMAKE_INSTALL_LIBDIR=${GLOG_INSTALL_DIR}/lib - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DWITH_GFLAGS=OFF - -DBUILD_TESTING=OFF - -DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE} - ${EXTERNAL_OPTIONAL_ARGS} - CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GLOG_INSTALL_DIR} - -DCMAKE_INSTALL_LIBDIR:PATH=${GLOG_INSTALL_DIR}/lib - -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - BUILD_BYPRODUCTS ${GLOG_LIBRARIES} -) -ENDIF() - -ADD_LIBRARY(glog STATIC IMPORTED GLOBAL) -SET_PROPERTY(TARGET glog PROPERTY IMPORTED_LOCATION ${GLOG_LIBRARIES}) -ADD_DEPENDENCIES(glog extern_glog gflags) -LINK_LIBRARIES(glog) \ No newline at end of file diff --git a/fast_tokenizer/cmake/external/gtest.cmake b/fast_tokenizer/cmake/external/gtest.cmake deleted file mode 100644 index 4b8709345587..000000000000 --- a/fast_tokenizer/cmake/external/gtest.cmake +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -IF(WITH_TESTING) - ENABLE_TESTING() -ENDIF() - -INCLUDE(GNUInstallDirs) -INCLUDE(ExternalProject) - -SET(GTEST_PREFIX_DIR ${THIRD_PARTY_PATH}/gtest) -SET(GTEST_INSTALL_DIR ${THIRD_PARTY_PATH}/install/gtest) -SET(GTEST_INCLUDE_DIR "${GTEST_INSTALL_DIR}/include" CACHE PATH "gtest include directory." FORCE) -set(GTEST_REPOSITORY ${GIT_URL}/google/googletest.git) -set(GTEST_TAG release-1.8.1) - -INCLUDE_DIRECTORIES(${GTEST_INCLUDE_DIR}) - -IF(WIN32) - set(GTEST_LIBRARIES - "${GTEST_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/gtest.lib" CACHE FILEPATH "gtest libraries." FORCE) - set(GTEST_MAIN_LIBRARIES - "${GTEST_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/gtest_main.lib" CACHE FILEPATH "gtest main libraries." FORCE) - string(REPLACE "/w " "" GTEST_CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - string(REPLACE "/w " "" GTEST_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - string(REPLACE "/W0 " "" GTEST_CMAKE_C_FLAGS "${GTEST_CMAKE_C_FLAGS}") - string(REPLACE "/W0 " "" GTEST_CMAKE_CXX_FLAGS "${GTEST_CMAKE_CXX_FLAGS}") -ELSE(WIN32) - set(GTEST_LIBRARIES - "${GTEST_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/libgtest.a" CACHE FILEPATH "gtest libraries." FORCE) - set(GTEST_MAIN_LIBRARIES - "${GTEST_INSTALL_DIR}/${CMAKE_INSTALL_LIBDIR}/libgtest_main.a" CACHE FILEPATH "gtest main libraries." FORCE) - set(GTEST_CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") - set(GTEST_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") -ENDIF(WIN32) - -ExternalProject_Add( - extern_gtest - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${GTEST_REPOSITORY} - GIT_TAG ${GTEST_TAG} - DEPENDS ${GTEST_DEPENDS} - PREFIX ${GTEST_PREFIX_DIR} - UPDATE_COMMAND "" - CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_CXX_FLAGS=${GTEST_CMAKE_CXX_FLAGS} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} - -DCMAKE_C_FLAGS=${GTEST_CMAKE_C_FLAGS} - -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} - -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} - -DCMAKE_INSTALL_PREFIX=${GTEST_INSTALL_DIR} - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DBUILD_GMOCK=ON - -Dgtest_disable_pthreads=ON - -Dgtest_force_shared_crt=ON - -DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE} - ${EXTERNAL_OPTIONAL_ARGS} - CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${GTEST_INSTALL_DIR} - -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - BUILD_BYPRODUCTS ${GTEST_LIBRARIES} - BUILD_BYPRODUCTS ${GTEST_MAIN_LIBRARIES} -) - -ADD_LIBRARY(gtest STATIC IMPORTED GLOBAL) -SET_PROPERTY(TARGET gtest PROPERTY IMPORTED_LOCATION ${GTEST_LIBRARIES}) -ADD_DEPENDENCIES(gtest extern_gtest) - -ADD_LIBRARY(gtest_main STATIC IMPORTED GLOBAL) -SET_PROPERTY(TARGET gtest_main PROPERTY IMPORTED_LOCATION ${GTEST_MAIN_LIBRARIES}) -ADD_DEPENDENCIES(gtest_main extern_gtest) \ No newline at end of file diff --git a/fast_tokenizer/cmake/external/icu.cmake b/fast_tokenizer/cmake/external/icu.cmake deleted file mode 100644 index cd604d384ef6..000000000000 --- a/fast_tokenizer/cmake/external/icu.cmake +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -include(CMakeParseArguments) -include(ExternalProject) -include (ByproductsICU) -SET(ICU_PREFIX_DIR ${THIRD_PARTY_PATH}/icu) -SET(ICU_INSTALL_DIR ${THIRD_PARTY_PATH}/install/icu) -if(ANDROID) - set(ICU_URL_PREFIX "https://bj.bcebos.com/fastdeploy/test") - # check ABI, toolchain - if((NOT ANDROID_ABI MATCHES "armeabi-v7a") AND (NOT ANDROID_ABI MATCHES "arm64-v8a")) - message(FATAL_ERROR "FastTokenizer for Android only support armeabi-v7a, arm64-v8a now.") - endif() - if(NOT ANDROID_TOOLCHAIN MATCHES "clang") - message(FATAL_ERROR "Currently, only support clang toolchain while cross compiling FastTokenizer for Android, but found ${ANDROID_TOOLCHAIN}.") - endif() - if (WITH_ICU_LITE) - set(ICU_REPOSITORY ${ICU_URL_PREFIX}/icu-lite-android-${ANDROID_ABI}.tgz) - else() - set(ICU_REPOSITORY ${ICU_URL_PREFIX}/icu-android-${ANDROID_ABI}.tgz) - endif() -else() - SET(ICU_REPOSITORY ${GIT_URL}/unicode-org/icu.git) -endif() -SET(ICU_TAG release-70-1) -set(FIND_OR_BUILD_ICU_DIR ${CMAKE_CURRENT_LIST_DIR}) - -set(HOST_CFLAGS "${CMAKE_C_FLAGS}") -set(HOST_CXXFLAGS "${CMAKE_CXX_FLAGS}") -set(HOST_CC "${CMAKE_C_COMPILER}") -set(HOST_CXX "${CMAKE_CXX_COMPILER}") -set(HOST_LDFLAGS "${CMAKE_MODULE_LINKER_FLAGS}") - -set(HOST_ENV_CMAKE ${CMAKE_COMMAND} -E env - CC=${HOST_CC} - CXX=${HOST_CXX} - CFLAGS=${HOST_CFLAGS} - CXXFLAGS=${HOST_CXXFLAGS} - LDFLAGS=${HOST_LDFLAGS} -) - -# predict host libraries -set(ICU_STATIC TRUE) -GetICUByproducts(${ICU_INSTALL_DIR} ICU_LIBRARIES ICU_INCLUDE_DIRS ICU_BASE_NAMES) -INCLUDE_DIRECTORIES(${ICU_INCLUDE_DIRS}) - -if(WIN32) -ExternalProject_Add( - extern_icu - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${ICU_REPOSITORY} - GIT_TAG ${ICU_TAG} - GIT_PROGRESS 1 - PREFIX ${ICU_PREFIX_DIR} - UPDATE_COMMAND "" - CONFIGURE_COMMAND msbuild ..\\extern_icu\\icu4c\\source\\allinone\\allinone.sln /p:Configuration=Release /p:Platform=x64 /p:RuntimeLibrary=MT_StaticRelease /p:SkipUWP=true - BUILD_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory ../extern_icu/icu4c/include ${ICU_INSTALL_DIR}/include - && ${CMAKE_COMMAND} -E copy_directory ../extern_icu/icu4c/lib64 ${ICU_INSTALL_DIR}/lib64 - BUILD_BYPRODUCTS ${ICU_LIBRARIES} -) -elseif(APPLE) -ExternalProject_Add( - extern_icu - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${ICU_REPOSITORY} - GIT_TAG ${ICU_TAG} - GIT_PROGRESS 1 - PREFIX ${ICU_PREFIX_DIR} - UPDATE_COMMAND "" - CONFIGURE_COMMAND ${HOST_ENV_CMAKE} ../extern_icu/icu4c/source/runConfigureICU "MacOSX/GCC" --enable-static --disable-shared --enable-rpath - BUILD_COMMAND make -j4 - INSTALL_COMMAND make install prefix="" DESTDIR=${ICU_INSTALL_DIR} install - BUILD_BYPRODUCTS ${ICU_LIBRARIES} -) -elseif(ANDROID) -ExternalProject_Add( - extern_icu - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - URL ${ICU_REPOSITORY} - PREFIX ${ICU_PREFIX_DIR} - CONFIGURE_COMMAND "" - UPDATE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND - ${CMAKE_COMMAND} -E remove_directory ${ICU_INSTALL_DIR} && - ${CMAKE_COMMAND} -E make_directory ${ICU_INSTALL_DIR} && - ${CMAKE_COMMAND} -E rename ${ICU_PREFIX_DIR}/src/extern_icu/lib/ ${ICU_INSTALL_DIR}/lib && - ${CMAKE_COMMAND} -E copy_directory ${ICU_PREFIX_DIR}/src/extern_icu/include ${ICU_INSTALL_DIR}/include - BUILD_BYPRODUCTS ${ICU_LIBRARIES} -) -else() -ExternalProject_Add( - extern_icu - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${ICU_REPOSITORY} - GIT_TAG ${ICU_TAG} - GIT_PROGRESS 1 - PREFIX ${ICU_PREFIX_DIR} - UPDATE_COMMAND "" - CONFIGURE_COMMAND ${HOST_ENV_CMAKE} ../extern_icu/icu4c/source/runConfigureICU "Linux/gcc" --enable-static --disable-shared --enable-rpath - BUILD_COMMAND make -j4 - INSTALL_COMMAND make install prefix="" DESTDIR=${ICU_INSTALL_DIR} install - BUILD_BYPRODUCTS ${ICU_LIBRARIES} -) -endif() - -list(LENGTH ICU_LIBRARIES ICU_LIB_LEN) -MATH(EXPR ICU_LIB_LEN "${ICU_LIB_LEN}-1") - -# icui18n icudata icuuc icuio icutu -foreach(ICU_IDX RANGE ${ICU_LIB_LEN}) - list(GET ICU_LIBRARIES ${ICU_IDX} ICU_LIB) - list(GET ICU_BASE_NAMES ${ICU_IDX} ICU_BASE_NAME) - ADD_LIBRARY("icu${ICU_BASE_NAME}" STATIC IMPORTED GLOBAL) - SET_PROPERTY(TARGET "icu${ICU_BASE_NAME}" PROPERTY IMPORTED_LOCATION ${ICU_LIB}) - ADD_DEPENDENCIES("icu${ICU_BASE_NAME}" extern_icu) - list(APPEND ICU_INTERFACE_LINK_LIBRARIES "icu${ICU_BASE_NAME}") -endforeach() - -if(WIN32) -ADD_LIBRARY("icudata" ALIAS "icudt") -endif() \ No newline at end of file diff --git a/fast_tokenizer/cmake/external/nlohmann_json.cmake b/fast_tokenizer/cmake/external/nlohmann_json.cmake deleted file mode 100644 index 9a34c5cca503..000000000000 --- a/fast_tokenizer/cmake/external/nlohmann_json.cmake +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -include(ExternalProject) - -set(JSON_PREFIX_DIR ${THIRD_PARTY_PATH}/json) -SET(JSON_REPOSITORY ${GIT_URL}/nlohmann/json.git) -SET(JSON_TAG v3.10.5) - -set(JSON_INCLUDE_DIR ${THIRD_PARTY_PATH}/json/src/extern_json/single_include) -include_directories(${JSON_INCLUDE_DIR}) - -ExternalProject_Add( - extern_json - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${JSON_REPOSITORY} - GIT_TAG ${JSON_TAG} - GIT_PROGRESS 1 - PREFIX ${JSON_PREFIX_DIR} - # If we explicitly leave the `UPDATE_COMMAND` of the ExternalProject_Add - # function in CMakeLists blank, it will cause another parameter GIT_TAG - # to be modified without triggering incremental compilation, and the - # third-party library version changes cannot be incorporated. - # reference: https://cmake.org/cmake/help/latest/module/ExternalProject.html - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) - -add_library(json INTERFACE) - -add_dependencies(json extern_json) diff --git a/fast_tokenizer/cmake/external/protobuf.cmake b/fast_tokenizer/cmake/external/protobuf.cmake deleted file mode 100644 index e5f3e19be7b5..000000000000 --- a/fast_tokenizer/cmake/external/protobuf.cmake +++ /dev/null @@ -1,295 +0,0 @@ -# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -INCLUDE(ExternalProject) -# Always invoke `FIND_PACKAGE(Protobuf)` for importing function protobuf_generate_cpp -IF(NOT WIN32) -FIND_PACKAGE(Protobuf QUIET) -ENDIF(NOT WIN32) -macro(UNSET_VAR VAR_NAME) - UNSET(${VAR_NAME} CACHE) - UNSET(${VAR_NAME}) -endmacro() - -UNSET_VAR(PROTOBUF_INCLUDE_DIR) -UNSET_VAR(PROTOBUF_FOUND) -UNSET_VAR(PROTOBUF_PROTOC_EXECUTABLE) -UNSET_VAR(PROTOBUF_PROTOC_LIBRARY) -UNSET_VAR(PROTOBUF_LITE_LIBRARY) -UNSET_VAR(PROTOBUF_LIBRARY) -UNSET_VAR(PROTOBUF_INCLUDE_DIR) -UNSET_VAR(Protobuf_PROTOC_EXECUTABLE) -function(protobuf_generate_python SRCS) - # shameless copy from https://github.com/Kitware/CMake/blob/master/Modules/FindProtobuf.cmake - if(NOT ARGN) - message(SEND_ERROR "Error: PROTOBUF_GENERATE_PYTHON() called without any proto files") - return() - endif() - - if(PROTOBUF_GENERATE_CPP_APPEND_PATH) - # Create an include path for each file specified - foreach(FIL ${ARGN}) - get_filename_component(ABS_FIL ${FIL} ABSOLUTE) - get_filename_component(ABS_PATH ${ABS_FIL} PATH) - list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) - if(${_contains_already} EQUAL -1) - list(APPEND _protobuf_include_path -I ${ABS_PATH}) - endif() - endforeach() - else() - set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR}) - endif() - if(DEFINED PROTOBUF_IMPORT_DIRS AND NOT DEFINED Protobuf_IMPORT_DIRS) - set(Protobuf_IMPORT_DIRS "${PROTOBUF_IMPORT_DIRS}") - endif() - - if(DEFINED Protobuf_IMPORT_DIRS) - foreach(DIR ${Protobuf_IMPORT_DIRS}) - get_filename_component(ABS_PATH ${DIR} ABSOLUTE) - list(FIND _protobuf_include_path ${ABS_PATH} _contains_already) - if(${_contains_already} EQUAL -1) - list(APPEND _protobuf_include_path -I ${ABS_PATH}) - endif() - endforeach() - endif() - - set(${SRCS}) - foreach(FIL ${ARGN}) - get_filename_component(ABS_FIL ${FIL} ABSOLUTE) - get_filename_component(FIL_WE ${FIL} NAME_WE) - if(NOT PROTOBUF_GENERATE_CPP_APPEND_PATH) - get_filename_component(FIL_DIR ${FIL} DIRECTORY) - if(FIL_DIR) - set(FIL_WE "${FIL_DIR}/${FIL_WE}") - endif() - endif() - list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}_pb2.py") - add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}_pb2.py" - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --python_out ${CMAKE_CURRENT_BINARY_DIR} ${_protobuf_include_path} ${ABS_FIL} - DEPENDS ${ABS_FIL} ${PROTOBUF_PROTOC_EXECUTABLE} - COMMENT "Running Python protocol buffer compiler on ${FIL}" - VERBATIM ) - endforeach() - - set(${SRCS} ${${SRCS}} PARENT_SCOPE) -endfunction() - -# Print and set the protobuf library information, -# finish this cmake process and exit from this file. -macro(PROMPT_PROTOBUF_LIB) - SET(protobuf_DEPS ${ARGN}) - - MESSAGE(STATUS "Protobuf protoc executable: ${PROTOBUF_PROTOC_EXECUTABLE}") - MESSAGE(STATUS "Protobuf-lite library: ${PROTOBUF_LITE_LIBRARY}") - MESSAGE(STATUS "Protobuf library: ${PROTOBUF_LIBRARY}") - MESSAGE(STATUS "Protoc library: ${PROTOBUF_PROTOC_LIBRARY}") - MESSAGE(STATUS "Protobuf version: ${PROTOBUF_VERSION}") - INCLUDE_DIRECTORIES(${PROTOBUF_INCLUDE_DIR}) - - # Assuming that all the protobuf libraries are of the same type. - IF(${PROTOBUF_LIBRARY} MATCHES ${CMAKE_STATIC_LIBRARY_SUFFIX}) - SET(protobuf_LIBTYPE STATIC) - ELSEIF(${PROTOBUF_LIBRARY} MATCHES "${CMAKE_SHARED_LIBRARY_SUFFIX}$") - SET(protobuf_LIBTYPE SHARED) - ELSE() - MESSAGE(FATAL_ERROR "Unknown library type: ${PROTOBUF_LIBRARY}") - ENDIF() - - ADD_LIBRARY(protobuf ${protobuf_LIBTYPE} IMPORTED GLOBAL) - SET_PROPERTY(TARGET protobuf PROPERTY IMPORTED_LOCATION ${PROTOBUF_LIBRARY}) - - ADD_LIBRARY(protobuf_lite ${protobuf_LIBTYPE} IMPORTED GLOBAL) - SET_PROPERTY(TARGET protobuf_lite PROPERTY IMPORTED_LOCATION ${PROTOBUF_LITE_LIBRARY}) - - ADD_LIBRARY(libprotoc ${protobuf_LIBTYPE} IMPORTED GLOBAL) - SET_PROPERTY(TARGET libprotoc PROPERTY IMPORTED_LOCATION ${PROTOC_LIBRARY}) - - ADD_EXECUTABLE(protoc IMPORTED GLOBAL) - SET_PROPERTY(TARGET protoc PROPERTY IMPORTED_LOCATION ${PROTOBUF_PROTOC_EXECUTABLE}) - # FIND_Protobuf.cmake uses `Protobuf_PROTOC_EXECUTABLE`. - # make `protobuf_generate_cpp` happy. - SET(Protobuf_PROTOC_EXECUTABLE ${PROTOBUF_PROTOC_EXECUTABLE}) - - FOREACH(dep ${protobuf_DEPS}) - ADD_DEPENDENCIES(protobuf ${dep}) - ADD_DEPENDENCIES(protobuf_lite ${dep}) - ADD_DEPENDENCIES(libprotoc ${dep}) - ADD_DEPENDENCIES(protoc ${dep}) - ENDFOREACH() - - RETURN() -endmacro() -macro(SET_PROTOBUF_VERSION) - EXEC_PROGRAM(${PROTOBUF_PROTOC_EXECUTABLE} ARGS --version OUTPUT_VARIABLE PROTOBUF_VERSION) - STRING(REGEX MATCH "[0-9]+.[0-9]+" PROTOBUF_VERSION "${PROTOBUF_VERSION}") -endmacro() - -set(PROTOBUF_ROOT "" CACHE PATH "Folder contains protobuf") -IF (WIN32) - SET(PROTOBUF_ROOT ${THIRD_PARTY_PATH}/install/protobuf) -ENDIF(WIN32) - -if (NOT "${PROTOBUF_ROOT}" STREQUAL "") - find_path(PROTOBUF_INCLUDE_DIR google/protobuf/message.h PATHS ${PROTOBUF_ROOT}/include NO_DEFAULT_PATH) - find_library(PROTOBUF_LIBRARY protobuf libprotobuf.lib PATHS ${PROTOBUF_ROOT}/lib NO_DEFAULT_PATH) - find_library(PROTOBUF_LITE_LIBRARY protobuf-lite libprotobuf-lite.lib PATHS ${PROTOBUF_ROOT}/lib NO_DEFAULT_PATH) - find_library(PROTOBUF_PROTOC_LIBRARY protoc libprotoc.lib PATHS ${PROTOBUF_ROOT}/lib NO_DEFAULT_PATH) - find_program(PROTOBUF_PROTOC_EXECUTABLE protoc PATHS ${PROTOBUF_ROOT}/bin NO_DEFAULT_PATH) - if (PROTOBUF_INCLUDE_DIR AND PROTOBUF_LIBRARY AND PROTOBUF_LITE_LIBRARY AND PROTOBUF_PROTOC_LIBRARY AND PROTOBUF_PROTOC_EXECUTABLE) - message(STATUS "Using custom protobuf library in ${PROTOBUF_ROOT}.") - SET(PROTOBUF_FOUND true) - SET_PROTOBUF_VERSION() - PROMPT_PROTOBUF_LIB() - else() - message(WARNING "Cannot find protobuf library in ${PROTOBUF_ROOT}") - endif() -endif() - -FUNCTION(build_protobuf TARGET_NAME BUILD_FOR_HOST) - STRING(REPLACE "extern_" "" TARGET_DIR_NAME "${TARGET_NAME}") - SET(PROTOBUF_SOURCES_DIR ${THIRD_PARTY_PATH}/${TARGET_DIR_NAME}) - SET(PROTOBUF_INSTALL_DIR ${THIRD_PARTY_PATH}/install/${TARGET_DIR_NAME}) - - SET(${TARGET_NAME}_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) - SET(PROTOBUF_INCLUDE_DIR "${PROTOBUF_INSTALL_DIR}/include" PARENT_SCOPE) - SET(${TARGET_NAME}_LITE_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf-lite${CMAKE_STATIC_LIBRARY_SUFFIX}" - PARENT_SCOPE) - SET(${TARGET_NAME}_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotobuf${CMAKE_STATIC_LIBRARY_SUFFIX}" - PARENT_SCOPE) - SET(${TARGET_NAME}_PROTOC_LIBRARY - "${PROTOBUF_INSTALL_DIR}/lib/libprotoc${CMAKE_STATIC_LIBRARY_SUFFIX}" - PARENT_SCOPE) - SET(${TARGET_NAME}_PROTOC_EXECUTABLE - "${PROTOBUF_INSTALL_DIR}/bin/protoc${CMAKE_EXECUTABLE_SUFFIX}" - PARENT_SCOPE) - - SET(PROTOBUF_REPO "https://github.com/protocolbuffers/protobuf.git") - SET(PROTOBUF_TAG "9f75c5aa851cd877fb0d93ccc31b8567a6706546") - SET(OPTIONAL_CACHE_ARGS "") - SET(OPTIONAL_ARGS "") - - IF(BUILD_FOR_HOST) - SET(OPTIONAL_ARGS - "-DCMAKE_C_COMPILER=${HOST_C_COMPILER}" - "-DCMAKE_CXX_COMPILER=${HOST_CXX_COMPILER}" - "-Dprotobuf_WITH_ZLIB=OFF" - "-DZLIB_ROOT:FILEPATH=${ZLIB_ROOT}") - SET(OPTIONAL_CACHE_ARGS "-DZLIB_ROOT:STRING=${ZLIB_ROOT}") - ELSE() - # protobuf have compile issue when use android stl c++_static - SET(PROTOBUF_REPO "https://github.com/tensor-tang/protobuf.git") - SET(PROTOBUF_TAG "mobile") - SET(OPTIONAL_ARGS "-Dprotobuf_WITH_ZLIB=OFF" - ${CROSS_COMPILE_CMAKE_ARGS} - "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" - "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}" - "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}" - "-DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG}" - "-DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE}" - "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" - "-DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE}" - "-DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG}") - ENDIF() - IF(WIN32) - SET(OPTIONAL_ARGS ${OPTIONAL_ARGS} "-DCMAKE_GENERATOR_PLATFORM=x64") - ENDIF() - - if(LITE_WITH_LIGHT_WEIGHT_FRAMEWORK) - ExternalProject_Add( - ${TARGET_NAME} - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${PROTOBUF_SOURCES_DIR} - SOURCE_SUBDIR cmake - UPDATE_COMMAND "" - GIT_REPOSITORY ${PROTOBUF_REPO} - GIT_TAG ${PROTOBUF_TAG} - GIT_PROGRESS 1 - CMAKE_ARGS - ${OPTIONAL_ARGS} - -Dprotobuf_BUILD_TESTS=OFF - -DCMAKE_SKIP_RPATH=ON - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE} - -DCMAKE_INSTALL_PREFIX=${PROTOBUF_INSTALL_DIR} - -DCMAKE_INSTALL_LIBDIR=lib - -DBUILD_SHARED_LIBS=OFF - CMAKE_CACHE_ARGS - -DCMAKE_INSTALL_PREFIX:PATH=${PROTOBUF_INSTALL_DIR} - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - -DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF - -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - ${OPTIONAL_CACHE_ARGS} - ) - else() - ExternalProject_Add( - ${TARGET_NAME} - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${PROTOBUF_SOURCES_DIR} - UPDATE_COMMAND "" - GIT_REPOSITORY ${PROTOBUF_REPO} - GIT_TAG ${PROTOBUF_TAG} - GIT_PROGRESS 1 - CONFIGURE_COMMAND - ${CMAKE_COMMAND} ${PROTOBUF_SOURCES_DIR}/src/${TARGET_NAME}/cmake - ${OPTIONAL_ARGS} - -Dprotobuf_BUILD_TESTS=OFF - -DCMAKE_SKIP_RPATH=ON - -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DCMAKE_BUILD_TYPE=${THIRD_PARTY_BUILD_TYPE} - -DCMAKE_INSTALL_PREFIX=${PROTOBUF_INSTALL_DIR} - -DCMAKE_INSTALL_LIBDIR=lib - -DBUILD_SHARED_LIBS=OFF - CMAKE_CACHE_ARGS - -DCMAKE_INSTALL_PREFIX:PATH=${PROTOBUF_INSTALL_DIR} - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - -DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF - -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON - ${OPTIONAL_CACHE_ARGS} - ) - endif() -ENDFUNCTION() - -SET(PROTOBUF_VERSION 3.1.0) - -IF(LITE_WITH_LIGHT_WEIGHT_FRAMEWORK) - build_protobuf(protobuf_host TRUE) - LIST(APPEND external_project_dependencies protobuf_host) - SET(PROTOBUF_PROTOC_EXECUTABLE ${protobuf_host_PROTOC_EXECUTABLE} - CACHE FILEPATH "protobuf executable." FORCE) -ENDIF() - -IF(NOT PROTOBUF_FOUND) - build_protobuf(extern_protobuf FALSE) - - SET(PROTOBUF_INCLUDE_DIR ${extern_protobuf_INCLUDE_DIR} - CACHE PATH "protobuf include directory." FORCE) - SET(PROTOBUF_LITE_LIBRARY ${extern_protobuf_LITE_LIBRARY} - CACHE FILEPATH "protobuf lite library." FORCE) - SET(PROTOBUF_LIBRARY ${extern_protobuf_LIBRARY} - CACHE FILEPATH "protobuf library." FORCE) - SET(PROTOBUF_PROTOC_LIBRARY ${extern_protobuf_PROTOC_LIBRARY} - CACHE FILEPATH "protoc library." FORCE) - - IF(LITE_WITH_LIGHT_WEIGHT_FRAMEWORK) - PROMPT_PROTOBUF_LIB(protobuf_host extern_protobuf) - ELSE() - SET(PROTOBUF_PROTOC_EXECUTABLE ${extern_protobuf_PROTOC_EXECUTABLE} - CACHE FILEPATH "protobuf executable." FORCE) - PROMPT_PROTOBUF_LIB(extern_protobuf) - ENDIF() - -ENDIF(NOT PROTOBUF_FOUND) \ No newline at end of file diff --git a/fast_tokenizer/cmake/external/pybind11.cmake b/fast_tokenizer/cmake/external/pybind11.cmake deleted file mode 100644 index 7f5f15d3e091..000000000000 --- a/fast_tokenizer/cmake/external/pybind11.cmake +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -include(ExternalProject) - -set(PYBIND_PREFIX_DIR ${THIRD_PARTY_PATH}/pybind) -SET(PYBIND_REPOSITORY ${GIT_URL}/pybind/pybind11.git) -SET(PYBIND_TAG v2.9.0) - -set(PYBIND_INCLUDE_DIR ${THIRD_PARTY_PATH}/pybind/src/extern_pybind/include) -include_directories(${PYBIND_INCLUDE_DIR}) - -ExternalProject_Add( - extern_pybind - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${PYBIND_REPOSITORY} - GIT_TAG ${PYBIND_TAG} - PREFIX ${PYBIND_PREFIX_DIR} - # If we explicitly leave the `UPDATE_COMMAND` of the ExternalProject_Add - # function in CMakeLists blank, it will cause another parameter GIT_TAG - # to be modified without triggering incremental compilation, and the - # third-party library version changes cannot be incorporated. - # reference: https://cmake.org/cmake/help/latest/module/ExternalProject.html - UPDATE_COMMAND "" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" -) - -add_library(pybind INTERFACE) - -add_dependencies(pybind extern_pybind) diff --git a/fast_tokenizer/cmake/external/python.cmake b/fast_tokenizer/cmake/external/python.cmake deleted file mode 100644 index 81da0782893b..000000000000 --- a/fast_tokenizer/cmake/external/python.cmake +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -INCLUDE(python_module) - -FIND_PACKAGE(PythonInterp ${PY_VERSION} REQUIRED) -FIND_PACKAGE(PythonLibs ${PY_VERSION} REQUIRED) - -if(WIN32) - execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" -"from distutils import sysconfig as s;import sys;import struct; -print(sys.prefix); -print(s.get_config_var('LDVERSION') or s.get_config_var('VERSION')); -" - RESULT_VARIABLE _PYTHON_SUCCESS - OUTPUT_VARIABLE _PYTHON_VALUES - ERROR_VARIABLE _PYTHON_ERROR_VALUE) - - if(NOT _PYTHON_SUCCESS EQUAL 0) - set(PYTHONLIBS_FOUND FALSE) - return() - endif() - - # Convert the process output into a list - string(REGEX REPLACE ";" "\\\\;" _PYTHON_VALUES ${_PYTHON_VALUES}) - string(REGEX REPLACE "\n" ";" _PYTHON_VALUES ${_PYTHON_VALUES}) - list(GET _PYTHON_VALUES 0 PYTHON_PREFIX) - list(GET _PYTHON_VALUES 1 PYTHON_LIBRARY_SUFFIX) - - # Make sure all directory separators are '/' - string(REGEX REPLACE "\\\\" "/" PYTHON_PREFIX ${PYTHON_PREFIX}) - - set(PYTHON_LIBRARY - "${PYTHON_PREFIX}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") - - # when run in a venv, PYTHON_PREFIX points to it. But the libraries remain in the - # original python installation. They may be found relative to PYTHON_INCLUDE_DIR. - if(NOT EXISTS "${PYTHON_LIBRARY}") - get_filename_component(_PYTHON_ROOT ${PYTHON_INCLUDE_DIR} DIRECTORY) - set(PYTHON_LIBRARY - "${_PYTHON_ROOT}/libs/Python${PYTHON_LIBRARY_SUFFIX}.lib") - endif() - - # raise an error if the python libs are still not found. - if(NOT EXISTS "${PYTHON_LIBRARY}") - message(FATAL_ERROR "Python libraries not found") - endif() - SET(PYTHON_LIBRARIES "${PYTHON_LIBRARY}") -endif(WIN32) - -# Fixme: Maybe find a static library. Get SHARED/STATIC by FIND_PACKAGE. -ADD_LIBRARY(python SHARED IMPORTED GLOBAL) -SET_PROPERTY(TARGET python PROPERTY IMPORTED_LOCATION ${PYTHON_LIBRARIES}) - -SET(py_env "") -IF(PYTHONINTERP_FOUND) - find_python_module(pip REQUIRED) - find_python_module(numpy REQUIRED) - find_python_module(wheel REQUIRED) - FIND_PACKAGE(NumPy REQUIRED) -ENDIF(PYTHONINTERP_FOUND) -INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR}) -INCLUDE_DIRECTORIES(${PYTHON_NUMPY_INCLUDE_DIR}) diff --git a/fast_tokenizer/cmake/external/re2.cmake b/fast_tokenizer/cmake/external/re2.cmake deleted file mode 100644 index 079dfaa3182c..000000000000 --- a/fast_tokenizer/cmake/external/re2.cmake +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -INCLUDE(ExternalProject) - -SET(RE2_PREFIX_DIR ${THIRD_PARTY_PATH}/re2) -SET(RE2_INSTALL_DIR ${THIRD_PARTY_PATH}/install/re2) -# As we add extra features for utf8proc, we use the non-official repo -SET(RE2_REPOSITORY ${GIT_URL}/google/re2.git) -SET(RE2_TAG 2022-04-01) - -IF(WIN32) - SET(RE2_LIBRARIES "${RE2_INSTALL_DIR}/lib/re2.lib") - add_definitions(-DRE2_STATIC) -ELSEIF(APPLE) - SET(RE2_LIBRARIES "${RE2_INSTALL_DIR}/lib/libre2.a") -ELSEIF(ANDROID) - SET(RE2_LIBRARIES "${RE2_INSTALL_DIR}/lib/libre2.a") -ELSE() - IF(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") - SET(RE2_LIBRARIES "${RE2_INSTALL_DIR}/lib/libre2.a") - ELSE() - file(READ "/etc/issue" ETC_ISSUE) - string(REGEX MATCH "Debian|Ubuntu" DIST ${ETC_ISSUE}) - IF(DIST STREQUAL "Debian") - SET(RE2_LIBRARIES "${RE2_INSTALL_DIR}/lib/libre2.a") - ELSEIF(DIST STREQUAL "Ubuntu") - SET(RE2_LIBRARIES "${RE2_INSTALL_DIR}/lib/libre2.a") - ELSE() - SET(RE2_LIBRARIES "${RE2_INSTALL_DIR}/lib64/libre2.a") - ENDIF() - ENDIF() -ENDIF() - -SET(RE2_INCLUDE_DIR ${RE2_INSTALL_DIR}/include) -INCLUDE_DIRECTORIES(${RE2_INCLUDE_DIR}) - -IF(ANDROID) -set(CROSS_COMPILE_CMAKE_ARGS - "-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}" - "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION}" - "-DCMAKE_ANDROID_ARCH_ABI=${CMAKE_ANDROID_ARCH_ABI}" - "-DCMAKE_ANDROID_NDK=${CMAKE_ANDROID_NDK}" - "-DCMAKE_ANDROID_STL_TYPE=${CMAKE_ANDROID_STL_TYPE}" - "-DANDROID_ABI=${CMAKE_ANDROID_ARCH_ABI}" - "-DANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN}" - "-DANDROID_STL=${CMAKE_ANDROID_STL_TYPE}" - "-DCMAKE_SYSTEM_PROCESSOR=${CMAKE_SYSTEM_PROCESSOR}" - "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_ANDROID_NDK}/build/cmake/android.toolchain.cmake" - "-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=${CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION}" - "-DANDROID_PLATFORM=android-${ANDROID_NATIVE_API_LEVEL}" - "-D__ANDROID_API__=${ANDROID_NATIVE_API_LEVEL}") - -ExternalProject_Add( - extern_re2 - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${RE2_REPOSITORY} - GIT_TAG ${RE2_TAG} - PREFIX ${RE2_PREFIX_DIR} - UPDATE_COMMAND "" - CMAKE_ARGS ${CROSS_COMPILE_CMAKE_ARGS} - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} - -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} - -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} - -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} - -DCMAKE_INSTALL_PREFIX:PATH=${RE2_INSTALL_DIR} - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - BUILD_BYPRODUCTS ${RE2_LIBRARIES} -) -ELSE() -ExternalProject_Add( - extern_re2 - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${RE2_REPOSITORY} - GIT_TAG ${RE2_TAG} - PREFIX ${RE2_PREFIX_DIR} - UPDATE_COMMAND "" - CMAKE_ARGS -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} - -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} - -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG} - -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE} - -DCMAKE_INSTALL_PREFIX:PATH=${RE2_INSTALL_DIR} - -DCMAKE_BUILD_TYPE:STRING=${THIRD_PARTY_BUILD_TYPE} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - BUILD_BYPRODUCTS ${RE2_LIBRARIES} -) -ENDIF() - -ADD_LIBRARY(re2 STATIC IMPORTED GLOBAL) -SET_PROPERTY(TARGET re2 PROPERTY IMPORTED_LOCATION ${RE2_LIBRARIES}) -ADD_DEPENDENCIES(re2 extern_re2) \ No newline at end of file diff --git a/fast_tokenizer/cmake/external/utf8proc.cmake b/fast_tokenizer/cmake/external/utf8proc.cmake deleted file mode 100644 index 460cbab819c4..000000000000 --- a/fast_tokenizer/cmake/external/utf8proc.cmake +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -INCLUDE(ExternalProject) - -SET(UTF8PROC_PREFIX_DIR ${THIRD_PARTY_PATH}/utf8proc) -SET(UTF8PROC_INSTALL_DIR ${THIRD_PARTY_PATH}/install/utf8proc) -# As we add extra features for utf8proc, we use the non-official repo -SET(UTF8PROC_REPOSITORY ${GIT_URL}/JuliaStrings/utf8proc.git) -SET(UTF8PROC_TAG v2.6.1) - -IF(WIN32) - SET(UTF8PROC_LIBRARIES "${UTF8PROC_INSTALL_DIR}/lib/utf8proc_static.lib") - add_definitions(-DUTF8PROC_STATIC) -ELSE(WIN32) - SET(UTF8PROC_LIBRARIES "${UTF8PROC_INSTALL_DIR}/lib/libutf8proc.a") -ENDIF(WIN32) - -INCLUDE_DIRECTORIES(${UTF8PROC_INSTALL_DIR}/include) - -ExternalProject_Add( - extern_utf8proc - ${EXTERNAL_PROJECT_LOG_ARGS} - ${SHALLOW_CLONE} - GIT_REPOSITORY ${UTF8PROC_REPOSITORY} - GIT_TAG ${UTF8PROC_TAG} - PREFIX ${UTF8PROC_PREFIX_DIR} - UPDATE_COMMAND "" - CMAKE_ARGS -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} - -DBUILD_SHARED=ON - -DBUILD_STATIC=ON - -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} - -DCMAKE_INSTALL_PREFIX:PATH=${UTF8PROC_INSTALL_DIR} - -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} - BUILD_BYPRODUCTS ${UTF8PROC_LIBRARIES} -) - -ADD_LIBRARY(utf8proc STATIC IMPORTED GLOBAL) -SET_PROPERTY(TARGET utf8proc PROPERTY IMPORTED_LOCATION ${UTF8PROC_LIBRARIES}) -ADD_DEPENDENCIES(utf8proc extern_utf8proc) \ No newline at end of file diff --git a/fast_tokenizer/cmake/generic.cmake b/fast_tokenizer/cmake/generic.cmake deleted file mode 100644 index 07266667383b..000000000000 --- a/fast_tokenizer/cmake/generic.cmake +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -function(cc_library TARGET_NAME) - set(options STATIC static SHARED shared INTERFACE interface) - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) - cmake_parse_arguments(cc_library "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if(WIN32) - # add libxxx.lib prefix in windows - set(${TARGET_NAME}_LIB_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}${TARGET_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}" CACHE STRING "output library name for target ${TARGET_NAME}") - endif(WIN32) - if(cc_library_SRCS) - if(cc_library_SHARED OR cc_library_shared) # build *.so - add_library(${TARGET_NAME} SHARED ${cc_library_SRCS}) - elseif(cc_library_INTERFACE OR cc_library_interface) - generate_dummy_static_lib(LIB_NAME ${TARGET_NAME} FILE_PATH ${target_SRCS} GENERATOR "generic.cmake:cc_library") - else() - add_library(${TARGET_NAME} STATIC ${cc_library_SRCS}) - endif() - if(cc_library_DEPS) - # remove link to python, see notes at: - # https://github.com/pybind/pybind11/blob/master/docs/compiling.rst#building-manually - if("${cc_library_DEPS};" MATCHES "python;") - list(REMOVE_ITEM cc_library_DEPS python) - add_dependencies(${TARGET_NAME} python) - if(WIN32) - target_link_libraries(${TARGET_NAME} ${PYTHON_LIBRARIES}) - else() - target_link_libraries(${TARGET_NAME} "-Wl,-undefined,dynamic_lookup") - endif(WIN32) - endif() - target_link_libraries(${TARGET_NAME} ${cc_library_DEPS} ${PUBLIC_DEPEND_LIBS}) - endif() - # For C++ 17 filesystem - # target_link_libraries(${TARGET_NAME} stdc++fs) - - # cpplint code style - foreach(source_file ${cc_library_SRCS}) - string(REGEX REPLACE "\\.[^.]*$" "" source ${source_file}) - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${source}.h) - list(APPEND cc_library_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/${source}.h) - endif() - endforeach() - - else(cc_library_SRCS) - if(cc_library_DEPS) - list(REMOVE_DUPLICATES cc_library_DEPS) - set(dummy_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_NAME}_dummy.c") - configure_file(${PROJECT_SOURCE_DIR}/cmake/dummy.c.in ${dummy_FILE_PATH} @ONLY) - if(cc_library_SHARED OR cc_library_shared) # build *.so - add_library(${TARGET_NAME} SHARED ${dummy_FILE_PATH}) - elseif(cc_library_INTERFACE OR cc_library_interface) - generate_dummy_static_lib(LIB_NAME ${TARGET_NAME} FILE_PATH ${dummy_FILE_PATH} GENERATOR "generic.cmake:cc_library") - else() - add_library(${TARGET_NAME} STATIC ${dummy_FILE_PATH}) - endif() - target_link_libraries(${TARGET_NAME} ${cc_library_DEPS}) - else() - message(FATAL_ERROR "Please specify source files or libraries in cc_library(${TARGET_NAME} ...).") - endif() - endif(cc_library_SRCS) -endfunction(cc_library) - -function(cc_test_build TARGET_NAME) - if(WITH_TESTING AND NOT "$ENV{CI_SKIP_CPP_TEST}" STREQUAL "ON") - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) - cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - add_executable(${TARGET_NAME} ${cc_test_SRCS}) - if(WIN32) - if("${cc_test_DEPS};" MATCHES "python;") - list(REMOVE_ITEM cc_test_DEPS python) - target_link_libraries(${TARGET_NAME} ${PYTHON_LIBRARIES}) - endif() - endif(WIN32) - get_property(os_dependency_modules GLOBAL PROPERTY OS_DEPENDENCY_MODULES) - target_link_libraries(${TARGET_NAME} ${cc_test_DEPS} ${os_dependency_modules} tokenizers_gtest_main gtest glog) - add_dependencies(${TARGET_NAME} ${cc_test_DEPS} gtest) - endif() -endfunction() - -function(cc_test_run TARGET_NAME) - if(WITH_TESTING AND NOT "$ENV{CI_SKIP_CPP_TEST}" STREQUAL "ON") - set(oneValueArgs "") - set(multiValueArgs COMMAND ARGS) - cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - add_test(NAME ${TARGET_NAME} - COMMAND ${cc_test_COMMAND} ${cc_test_ARGS} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) - # No unit test should exceed 2 minutes. - if (WIN32) - set_tests_properties(${TARGET_NAME} PROPERTIES TIMEOUT 150) - endif() - if (APPLE) - set_tests_properties(${TARGET_NAME} PROPERTIES TIMEOUT 20) - endif() - elseif(WITH_TESTING AND NOT TEST ${TARGET_NAME}) - add_test(NAME ${TARGET_NAME} COMMAND ${CMAKE_COMMAND} -E echo CI skip ${TARGET_NAME}.) - endif() -endfunction() - -function(cc_test TARGET_NAME) - # The environment variable `CI_SKIP_CPP_TEST` is used to skip the compilation - # and execution of test in CI. `CI_SKIP_CPP_TEST` is set to ON when no files - # other than *.py are modified. - if(WITH_TESTING) - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS ARGS) - cmake_parse_arguments(cc_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - cc_test_build(${TARGET_NAME} - SRCS ${cc_test_SRCS} - DEPS ${cc_test_DEPS}) - add_test(NAME ${TARGET_NAME} COMMAND ${CMAKE_COMMAND} -E echo CI skip ${TARGET_NAME}.) - endif() -endfunction(cc_test) - -# create a dummy source file, then create a static library. -# LIB_NAME should be the static lib name. -# FILE_PATH should be the dummy source file path. -# GENERATOR should be the file name invoke this function. -# CONTENT should be some helpful info. -# example: generate_dummy_static_lib(mylib FILE_PATH /path/to/dummy.c GENERATOR mylib.cmake CONTENT "helpful info") -function(generate_dummy_static_lib) - set(options "") - set(oneValueArgs LIB_NAME FILE_PATH GENERATOR CONTENT) - set(multiValueArgs "") - cmake_parse_arguments(dummy "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - if(NOT dummy_LIB_NAME) - message(FATAL_ERROR "You must provide a static lib name.") - endif() - if(NOT dummy_FILE_PATH) - set(dummy_FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${dummy_LIB_NAME}_dummy.c") - endif() - if(NOT dummy_GENERATOR) - message(FATAL_ERROR "You must provide a generator file name.") - endif() - if(NOT dummy_CONTENT) - set(dummy_CONTENT "${dummy_LIB_NAME}_dummy.c for lib ${dummy_LIB_NAME}") - endif() - - configure_file(${PROJECT_SOURCE_DIR}/cmake/dummy.c.in ${dummy_FILE_PATH} @ONLY) - add_library(${dummy_LIB_NAME} STATIC ${dummy_FILE_PATH}) -endfunction() - -function(paddle_protobuf_generate_cpp SRCS HDRS) - if(NOT ARGN) - message( - SEND_ERROR - "Error: paddle_protobuf_generate_cpp() called without any proto files") - return() - endif() - - set(${SRCS}) - set(${HDRS}) - - foreach(FIL ${ARGN}) - get_filename_component(ABS_FIL ${FIL} ABSOLUTE) - get_filename_component(FIL_WE ${FIL} NAME_WE) - set(_protobuf_protoc_src "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc") - set(_protobuf_protoc_hdr "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h") - list(APPEND ${SRCS} "${_protobuf_protoc_src}") - list(APPEND ${HDRS} "${_protobuf_protoc_hdr}") - - add_custom_command( - OUTPUT "${_protobuf_protoc_src}" "${_protobuf_protoc_hdr}" - COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}" - COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} -I${CMAKE_CURRENT_SOURCE_DIR} - --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" ${ABS_FIL} - # Set `EXTERN_PROTOBUF_DEPEND` only if need to compile `protoc.exe`. - DEPENDS ${ABS_FIL} ${EXTERN_PROTOBUF_DEPEND} - COMMENT "Running C++ protocol buffer compiler on ${FIL}" - VERBATIM) - endforeach() - - set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) - set(${SRCS} - ${${SRCS}} - PARENT_SCOPE) - set(${HDRS} - ${${HDRS}} - PARENT_SCOPE) -endfunction() - -function(proto_library TARGET_NAME) - set(oneValueArgs "") - set(multiValueArgs SRCS DEPS) - cmake_parse_arguments(proto_library "${options}" "${oneValueArgs}" - "${multiValueArgs}" ${ARGN}) - set(proto_srcs) - set(proto_hdrs) - paddle_protobuf_generate_cpp(proto_srcs proto_hdrs ${proto_library_SRCS}) - cc_library( - ${TARGET_NAME} - SRCS ${proto_srcs} - DEPS ${proto_library_DEPS} protobuf) -endfunction() \ No newline at end of file diff --git a/fast_tokenizer/cmake/python_module.cmake b/fast_tokenizer/cmake/python_module.cmake deleted file mode 100644 index 8fdccc17e9b0..000000000000 --- a/fast_tokenizer/cmake/python_module.cmake +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -function(find_python_module module) - string(TOUPPER ${module} module_upper) - if(NOT PY_${module_upper}) - if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") - set(${module}_FIND_REQUIRED TRUE) - else() - set(${module}_FIND_REQUIRED FALSE) - endif() - # A module's location is usually a directory, but for binary modules - # it's a .so file. - execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))" - RESULT_VARIABLE _${module}_status - OUTPUT_VARIABLE _${module}_location - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT _${module}_status) - set(PY_${module_upper} ${_${module}_location} CACHE STRING - "Location of Python module ${module}") - endif(NOT _${module}_status) - endif(NOT PY_${module_upper}) - find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper}) - if(NOT PY_${module_upper}_FOUND AND ${module}_FIND_REQUIRED) - message(FATAL_ERROR "python module ${module} is not found") - endif() - - execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import sys, ${module}; sys.stdout.write(${module}.__version__)" - OUTPUT_VARIABLE _${module}_version - RESULT_VARIABLE _${module}_status - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT _${module}_status) - set(PY_${module_upper}_VERSION ${_${module}_version} CACHE STRING - "Version of Python module ${module}") - endif(NOT _${module}_status) - - set(PY_${module_upper}_FOUND ${PY_${module_upper}_FOUND} PARENT_SCOPE) - set(PY_${module_upper}_VERSION ${PY_${module_upper}_VERSION} PARENT_SCOPE) -endfunction(find_python_module) diff --git a/fast_tokenizer/cmake/third_party.cmake b/fast_tokenizer/cmake/third_party.cmake deleted file mode 100644 index ef17fc6bf3dc..000000000000 --- a/fast_tokenizer/cmake/third_party.cmake +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -include(ExternalProject) - -set(THIRD_PARTY_PATH "${CMAKE_BINARY_DIR}/third_party" CACHE STRING - "A path setting third party libraries download & build directories.") - -include(external/icu) -if(WITH_TESTING) - include(external/gtest) -endif() -include(external/gflags) -include(external/glog) -include(external/re2) -include(external/nlohmann_json) -include(external/dart) # For trie -if (WITH_PYTHON) - include(external/python) - include(external/pybind11) -endif() \ No newline at end of file diff --git a/fast_tokenizer/docs/compile/README.md b/fast_tokenizer/docs/compile/README.md deleted file mode 100644 index 6ae8c791ae7c..000000000000 --- a/fast_tokenizer/docs/compile/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# FastTokenizer 编译指南 - -本文档说明编译 FastTokenizer C++ 库、Python 库以及 Android 库三种编译过程,根据编译的平台参考如下文档 - -- [Linux & Mac 编译](./how_to_build_linux_and_mac.md) -- [Windows 编译](./how_to_build_windows.md) -- [Android 编译](./how_to_build_android.md) - -FastTokenizer 使用 CMake 编译,其中编译过程中,各平台上编译选项如下表所示 - -| 选项 | 作用 | 备注 | -|:---- | :--- | :--- | -| WITH_PYTHON | 是否编译为 Python 库,默认为是,否则为 C++ 库|| -| WITH_TESTING | 是否编译 C++ 单测,默认为否 || -| WITH_ICU_LITE | 是否与 ICU Lite 依赖包联编,打开后可减小 FastTokenizer 库体积,默认为否 | 只能用于 Andriod 编译,打开后 FastTokenizer 库体积大小从 **32 M 减少到 7.4 M**,但只能对中英文进行分词。| -| USE_ABI0 | 是否编译_GLIBCXX_USE_CXX11_ABI=0, 默认为OFF。| diff --git a/fast_tokenizer/docs/compile/how_to_build_android.md b/fast_tokenizer/docs/compile/how_to_build_android.md deleted file mode 100644 index 40c1cfe375db..000000000000 --- a/fast_tokenizer/docs/compile/how_to_build_android.md +++ /dev/null @@ -1,46 +0,0 @@ -# Android 编译 - -FastTokenizer 提供两种版本 Android 库,分别是常规版本以及轻量版本。常规版本的 FastTokenizer Android 库功能齐全,可支持任意语言的分词功能,库体积大约为 **32 M**;轻量版本主要支持中文和英文两种语言的分词,库体积约为 **7.4 M**。开发者可以根据自己实际需求选择合适的版本安装,以下将分别介绍这两种版本的编译方式。 - -## 环境依赖 - -- cmake >= 3.10 -- NDK >= 20 - -## 配置NDK -```bash -wget https://dl.google.com/android/repository/android-ndk-r20b-linux-x86_64.zip -unzip android-ndk-r20b-linux-x86_64.zip # 会解压缩到 android-ndk-r20b 目录 -export NDK_ROOT=${PWD}/android-ndk-r20b -``` - -## 编译 C++ 库方法 - -### 常规版本 - -```bash -git clone https://github.com/PaddlePaddle/PaddleNLP.git -cd PaddleNLP/fast_tokenizer -mkdir build & cd build -cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_ABI="arm64-v8a" -DANDROID_NATIVE_API_LEVEL=android-21 -DANDROID_STL=c++_shared -DWITH_TESTING=OFF -DWITH_PYTHON=OFF -DANDROID_TOOLCHAIN=clang -make -j8 -``` - -### 轻量版本 - -``` -git clone https://github.com/PaddlePaddle/PaddleNLP.git -cd PaddleNLP/fast_tokenizer -mkdir build & cd build -cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_ABI="arm64-v8a" -DANDROID_NATIVE_API_LEVEL=android-21 -DANDROID_STL=c++_shared -DWITH_TESTING=OFF -DWITH_PYTHON=OFF -DANDROID_TOOLCHAIN=clang -DWITH_ICU_LITE=ON -make -j8 -``` - -### 库体积压缩 - -编译后的 C++ 库在当前目录下的 `cpp` 目录下。可以选择使用 strip 减少库体积: -```shell -$NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/aarch64-linux-android/bin/strip libcore_tokenizers.so -``` - -更多编译选项说明参考[编译指南](./README.md) diff --git a/fast_tokenizer/docs/compile/how_to_build_linux_and_mac.md b/fast_tokenizer/docs/compile/how_to_build_linux_and_mac.md deleted file mode 100644 index cd13724aef2d..000000000000 --- a/fast_tokenizer/docs/compile/how_to_build_linux_and_mac.md +++ /dev/null @@ -1,36 +0,0 @@ -# Linux & Mac编译 - -## 环境依赖 - -- cmake >= 3.10 -- gcc >= 8.2.0 - -## 编译 C++ 库方法 - -```bash -git clone https://github.com/PaddlePaddle/PaddleNLP.git -cd PaddleNLP/fast_tokenizer -mkdir build & cd build -cmake .. -DWITH_PYTHON=OFF -DWITH_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -make -j8 -``` - -编译后的 C++ 库在当前目录下的 `cpp` 目录下。 - -## 编译 Python 库方法 - -```bash -git clone https://github.com/PaddlePaddle/PaddleNLP.git -cd PaddleNLP/fast_tokenizer -mkdir build & cd build -# 设置 Python 环境 -export LD_LIBRARY_PATH=/opt/_internal/cpython-3.6.0/lib/:${LD_LIBRARY_PATH} -export PATH=/opt/_internal/cpython-3.6.0/bin/:${PATH} - -cmake .. -DWITH_PYTHON=ON -DWITH_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -make -j8 -``` - -编译后的 wheel 包即在当前目录下的 `dist` 目录中 - -更多编译选项说明参考[编译指南](./README.md) diff --git a/fast_tokenizer/docs/compile/how_to_build_windows.md b/fast_tokenizer/docs/compile/how_to_build_windows.md deleted file mode 100644 index 4796b0418034..000000000000 --- a/fast_tokenizer/docs/compile/how_to_build_windows.md +++ /dev/null @@ -1,42 +0,0 @@ -# Windows 编译 - -## 环境依赖 - -- cmake >= 3.10 -- VS 2019 -- ninja -- cmake >= 3.10 - -以上依赖安装好后,在 Windows 菜单打开`x64 Native Tools Command Prompt for VS 2019`命令工具即可进行下面的编译环节。 - -## 编译 C++ 库方法 - -```bash -git clone https://github.com/PaddlePaddle/PaddleNLP.git -cd PaddleNLP/fast_tokenizer -mkdir build & cd build -cmake .. -G "Ninja" -DWITH_PYTHON=OFF -DWITH_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -ninja -j8 -``` - -编译后的 C++ 库在当前目录下的`cpp`目录下。 - -## 编译 Python 库方法 - -```bash -git clone https://github.com/PaddlePaddle/PaddleNLP.git -cd PaddleNLP/fast_tokenizer -mkdir build & cd build -# 需要指定 Python 库 -cmake .. -G "Ninja" -DWITH_PYTHON=ON ^ - -DWITH_TESTING=OFF ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DPYTHON_EXECUTABLE=C:\Python37\python.exe ^ - -DPYTHON_INCLUDE_DIR=C:\Python37\include ^ - -DPYTHON_LIBRARY=C:\Python37\libs\python3%%x.lib -ninja -j8 -``` - -编译后的 wheel 包即在当前目录下的 `dist` 目录中 - -更多编译选项说明参考[编译指南](./README.md) diff --git a/fast_tokenizer/docs/cpp/README.md b/fast_tokenizer/docs/cpp/README.md deleted file mode 100644 index 7b8976efd4de..000000000000 --- a/fast_tokenizer/docs/cpp/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# FastTokenizer C++ 库使用教程 - -## 1. 快速安装 - -当前版本 FastTokenizer C++ 库支持不同的操作系统以及硬件平台,并为以下平台提供预编译包: -|系统|下载地址| -|---|---| -|Linux-x64| [fast_tokenizer-linux-x64-1.0.2.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-linux-x64-1.0.2.tgz) | -|Linux-aarch64| [fast_tokenizer-linux-aarch64-1.0.2.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-linux-aarch64-1.0.2.tgz) | -|Windows| [fast_tokenizer-win-x64-1.0.2.zip](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-win-x64-1.0.2.zip) | -|MacOS-x64| [fast_tokenizer-osx-x86_64-1.0.2.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-osx-x86_64-1.0.2.tgz) | -|MacOS-arm64| [fast_tokenizer-osx-arm64-1.0.2.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-osx-arm64-1.0.2.tgz) | -|Android-arm64-v8a| [fast_tokenizer-android-arm64-v8a-1.0.2.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-android-arm64-v8a-1.0.2.tgz) | -|Android-armeabi-v7a| [fast_tokenizer-android-armeabi-v7a-1.0.2.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-android-armeabi-v7a-1.0.2.tgz) | -|Android-lite-arm64-v8a| [fast_tokenizer-lite-android-arm64-v8a-1.0.2.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-lite-android-arm64-v8a-1.0.2.tgz) | -|Android-lite-armeabi-v7a| [fast_tokenizer-lite-android-armeabi-v7a-1.0.2.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-lite-android-armeabi-v7a-1.0.2.tgz) | - -### 环境依赖 - -#### 系统环境要求 -|系统|版本|架构| -|---|---|---| -|Linux|Ubuntu 16.04+,CentOS 7+|x64, aarch64| -|Windows|10+|x64| -|MacOS| 11.4+|x64, arm64| -|Android| - |arm64-v8a, armeabi-v7a| - -#### Linux,Mac 编译环境要求 -|依赖|版本| -|---|---| -|cmake|>=16.0| -|gcc|>=8.2.0| - -#### Windows 编译环境要求 -|依赖|版本| -|---|---| -|cmake|>=16.0| -|VisualStudio|2019| - -### 下载解压 - -```shell -wget -c https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-linux-x64-1.0.2.tgz - -tar xvfz fast_tokenizer-linux-x64-1.0.2.tgz -# 解压后为fast_tokenizer目录 -``` - -解压后得到fast_tokenizer目录,该目录的结构如下: - -```shell - -fast_tokenizer -|__ commit.log # 编译时的commit id -|__ FastTokenizer.cmake # FastTokenizer CMake文件,定义了头文件目录、动态链接库目录变量 -|__ include # FastTokenizer的头文件目录 -|__ lib # FastTokenizer的动态链接库目录 -|__ third_party # FastTokenizer依赖的第三方库目录 - -``` - -推荐用户直接使用 cmake 方式引入 FastTokenizer 库。在 CMake 引入 FastTokenizer 时,只需添加一行 `include(FastTokenizer.cmake)`,即可获取 FastTokenizer 的预定义的 CMake 变量 `FAST_TOKENIZER_INCS` 和 `FAST_TOKENIZER_LIBS`,分别指定 FastTokenizer 的头文件目录以及动态链接库目录。 - - -## 2. 快速开始 - -目前 FastTokenizer 提供了以下 C++ 使用示例。 - -[ErnieFastTokenizer C++示例](../../examples/ernie-3.0/README.md) - -[ClipFastTokenizer C++示例](../../examples/clip/README.md) diff --git a/fast_tokenizer/docs/pipeline/README.md b/fast_tokenizer/docs/pipeline/README.md deleted file mode 100644 index 16da52f90ef4..000000000000 --- a/fast_tokenizer/docs/pipeline/README.md +++ /dev/null @@ -1,254 +0,0 @@ -# FastTokenizer Pipeline - -当我们使用 Tokenizer 的 `Tokenizer.encode` 或者 `Tokenizer.encode_batch` 方法进行分词时,会经历如下四个阶段:Normalize、PreTokenize、 Model 以及 PostProcess。针对这四个阶段,FastTokenizer 提供 Normalizer、PreTokenizer、Model 以及 PostProcessor 四个组件分别完成四个阶段所需要的工作。下面将详细介绍四大组件具体负责的工作,并通过示例介绍如何组合四个组件定义一个 Tokenizer。 - -## Normalizer - -Normalizer 组件主要用于将原始字符串标准化,输出标准化的字符串,常见的标准化字符串操作有大小写转换、半角全角转换等。 FastTokenizer 所有 Normalizer 类都继承自 `normalizers.Normalizer`,命名方式均为 `normalizers.*Normalizer`。 FastTokenizer 还支持将现有 Normalizer 类进行组合得到一个 Normalizer 序列,用户可以通过调用 `normalizers.SequenceNormalizer` 使用已有的 Normalizer 自定义新的 Normalizer。下面将分别展示 Python 以及 C++ 上使用示例。 - -### Python 示例 - -```python -import fast_tokenizer -from fast_tokenizer.normalizers import LowercaseNormalizer, SequenceNormalizer, NFDNormalizer, StripAccentsNormalizer - -normalizer = SequenceNormalizer([NFDNormalizer(), StripAccentsNormalizer() LowercaseNormalizer()]) -print(normalizer.normalize_str("Héllò hôw are ü?")) -# hello how are u? -``` - -### C++ 示例 - -```c++ - -#include -#include "fast_tokenizer/normalizers/normalizers.h" -using namespace paddlenlp::fast_tokenizer; - -int main() { - normalizers::NFDNormalizer n1; - normalizers::StripAccentsNormalizer n2; - normalizers::LowercaseNormalizer n3; - normalizers::SequenceNormalizer normalizer({&n1, &n2, &n3}); - normalizers::NormalizedString normalized("Héllò hôw are ü?"); - normalizer(&normalized); - // Expected output - // normalized string: hello how are u? - // original string: Héllò hôw are ü? - std::cout << "normalized string: " << normalized.GetStr() << std::endl; - std::cout << "original string: " << normalized.GetOrignalStr() << std::endl; -} - -``` - -## PreTokenizer - -PreTokenizer 组件主要使用简单的分词方法,将标准化的字符串进行预切词,得到较大粒度的词组(word),例如按照标点、空格等方式进行分词。FastTokenizer 所有 PreTokenizer 类都继承自 `normalizers.PreTokenizer`,命名方式均为 `normalizers.*PreTokenizer`。 下面将分别展示 Python 以及 C++ 上使用空格对文本进行分词的使用示例。 - -### Python 示例 - -```python -import fast_tokenizer -from fast_tokenizer.pretokenizers import WhitespacePreTokenizer -pretokenizer = WhitespacePreTokenizer() -print(pretokenizer.pretokenize_str("Hello! How are you? I'm fine, thank you.")) -# [('Hello!', (0, 6)), ('How', (7, 10)), ('are', (11, 14)), ('you?', (15, 19)), ("I'm", (20, 23)), ('fine,', (24, 29)), ('thank', (30, 35)), ('you.', (36, 40))] -``` - -### C++ 示例 - -```c++ - -#include -#include "fast_tokenizer/pretokenizers/pretokenizers.h" - -using namespace paddlenlp::fast_tokenizer; - -int main() { - pretokenizers::WhitespacePreTokenizer pretokenizer; - pretokenizers::PreTokenizedString pretokenized( - "Hello! How are you? I'm fine, thank you."); - pretokenizer(&pretokenized); - auto&& splits = pretokenized.GetSplits(true, core::OffsetType::CHAR); - for (auto&& split : splits) { - auto&& value = std::get<0>(split); - auto&& offset = std::get<1>(split); - std::cout << "(" << value << ", (" << offset.first << ", " << offset.second - << ")" - << ")" << std::endl; - } - return 0; -} - -// (Hello!, (0, 6)) -// (How, (7, 10)) -// (are, (11, 14)) -// (you?, (15, 19)) -// (I'm, (20, 23)) -// (fine,, (24, 29)) -// (thank, (30, 35)) -// (you., (36, 40)) - -``` - -## Model - -Model 组件是 FastTokenizer 核心模块,用于将粗粒度词组按照一定的算法进行切分,得到细粒度的 Token(word piece)及其对应的在词表中的 id,目前支持的切词算法包括 FastWordPiece[1]、WordPiece、BPE 以及 Unigram。其中,`FastWordPiece` 是 "Fast WordPiece Tokenization" 提出的基于`MinMaxMatch`匹配算法的一种分词算法。原有 `WordPiece` 算法的时间复杂度与序列长度为二次方关系,在对长文本进行分词操作时,时间开销比较大。而 `FastWordPiece` 算法通过 `Aho–Corasick` 算法避免 Token 失配时从头匹配,将 `WordPiece` 算法的时间复杂度降低为与序列长度的线性关系,大大提升了分词效率。下面是 `FastWordPiece` 类的初始化示例。 - -### Python 示例 - -```python -import fast_tokenizer -from fast_tokenizer.models import FastWordPiece - -# Initialize model from ernie 3.0 vocab file -model = FastWordPiece.from_file("ernie-3.0-medium-vocab.txt", with_pretokenization=True) -print(model.tokenize("我爱中国!")) -# [id: 75 value:我 offset: (0, 3), id: 329 value:爱 offset: (3, 6), id: 12 value:中 offset: (6, 9), id: 20 value:国 offset: (9, 12), id: 12046 value:! offset: (12, 13)] -``` - -### C++ 示例 - -```c++ - -#include -#include - -#include "fast_tokenizer/models/models.h" - -using namespace paddlenlp::fast_tokenizer; - -int main() { - std::string text = "我爱中国!"; - auto model = models::FastWordPiece::GetFastWordPieceFromFile( - "ernie_vocab.txt", "[UNK]", 100, "##", true); - std::vector results = model.Tokenize(text); - for (const core::Token& token : results) { - std::cout << "id: " << token.id_ << ", value: " << token.value_ - << ", offset: (" << token.offset_.first << ", " - << token.offset_.second << ")." << std::endl; - } - return 0; -} - -// id: 75, value: 我, offset: (0, 3). -// id: 329, value: 爱, offset: (3, 6). -// id: 12, value: 中, offset: (6, 9). -// id: 20, value: 国, offset: (9, 12). -// id: 12044, value: !, offset: (12, 15). -``` - -## PostProcessor - -PostProcess 组件主要执行 Transformer 类模型的文本序列的后处理逻辑,比如添加 [SEP] 等特殊 Token,并且会将前面分词得到的结果转为一个 `Encoding` 的结构体,包含 token_ids, type_ids, offset, position_ids 等模型所需要的信息。FastTokenizer 所有 PostProcessor 类都继承自 `normalizers.PostProcessor`,命名方式均为 `normalizers.*PostProcessor`。 - -## Tokenizer - -Tokenizer 对象在运行`Tokenizer.encode` 或者 `Tokenizer.encode_batch` 方法进行分词时,通过调用各个阶段组件的回调函数运行不同阶段的处理逻辑。所以我们定义 Tokenizer 对象时,需要设置各个阶段的组件。下面将通过代码示例展示如何定义 ERNIE 模型的 Tokenizer。 - -### Python 示例 - -```python - -import fast_tokenizer -from fast_tokenizer import Tokenizer -from fast_tokenizer.models import FastWordPiece -from fast_tokenizer.normalizers import BertNormalizer -from fast_tokenizer.pretokenizers import BertPreTokenizer - -# 1. Initialize model from ernie 3.0 vocab file -model = FastWordPiece.from_file("ernie-3.0-medium-vocab.txt") - -# 2. Use model to initialize a tokenizer object -tokenizer = Tokenizer(model) - -# 3. Set a normalizer -tokenizer.normalizer = BertNormalizer( - clean_text=True, - handle_chinese_chars=True, - strip_accents=True, - lowercase=True, -) - -# 4. Set a pretokenizer -tokenizer.pretokenizer = BertPreTokenizer() - -print(tokenizer.encode("我爱中国!")) - -# The Encoding content: -# ids: 75, 329, 12, 20, 12046 -# type_ids: 0, 0, 0, 0, 0 -# tokens: 我, 爱, 中, 国, ! -# offsets: (0, 1), (1, 2), (2, 3), (3, 4), (4, 5) -# special_tokens_mask: 0, 0, 0, 0, 0 -# attention_mask: 1, 1, 1, 1, 1 -# sequence_ranges: -``` - -针对 ERNIE、BERT 这类常见模型,FastTokenizer Python 库 已经定义好这类模型的 Tokenizer,可以通过 `from fast_tokenizer import ErnieFastTokenizer` 直接使用。 - -### C++ 示例 - -```c++ - -#include -#include - -#include "fast_tokenizer/core/tokenizer.h" -#include "fast_tokenizer/models/models.h" -#include "fast_tokenizer/normalizers/normalizers.h" -#include "fast_tokenizer/postprocessors/postprocessors.h" -#include "fast_tokenizer/pretokenizers/pretokenizers.h" - -using namespace paddlenlp::fast_tokenizer; - -int main() { - std::vector texts{"我爱中国!"}; - core::Tokenizer tokenizer; - - // 1. Set model - auto model = models::FastWordPiece::GetFastWordPieceFromFile( - "ernie_vocab.txt", "[UNK]", 100, "##", true); - tokenizer.SetModel(model); - - // 2. Set Normalizer - normalizers::BertNormalizer normalizer( - /* clean_text = */ true, - /* handle_chinese_chars = */ true, - /* strip_accents= */ true, - /* lowercase = */ true); - tokenizer.SetNormalizer(normalizer); - - // 3. Set Pretokenizer - pretokenizers::BertPreTokenizer pretokenizer; - tokenizer.SetPreTokenizer(pretokenizer); - - // 4. Set PostProcessor - postprocessors::BertPostProcessor postprocessor; - tokenizer.SetPostProcessor(postprocessor); - - std::vector encodings; - tokenizer.EncodeBatchStrings(texts, &encodings); - - for (auto encoding : encodings) { - std::cout << encoding.DebugString() << std::endl; - } - return 0; -} - -// The Encoding content: -// ids: 101, 75, 329, 12, 20, 12044, 102 -// type_ids: 0, 0, 0, 0, 0, 0, 0 -// tokens: [CLS], 我, 爱, 中, 国, !, [SEP] -// offsets: (0, 0), (0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (0, 0) -// special_tokens_mask: 1, 0, 0, 0, 0, 0, 1 -// attention_mask: 1, 1, 1, 1, 1, 1, 1 -// sequence_ranges: {0 : (1, 6) }, -``` - -针对 ERNIE、BERT 这类常见模型,FastTokenizer C++ 库 已经定义好这类模型的 Tokenizer,可以通过 `paddlenlp::fast_tokenizer::tokenizers_impl::ErnieFastTokenizer` 直接使用。 - - -## 参考文献 - -- [1] Xinying Song, Alex Salcianuet al. "Fast WordPiece Tokenization", EMNLP, 2021 diff --git a/fast_tokenizer/docs/python/README.md b/fast_tokenizer/docs/python/README.md deleted file mode 100644 index 86720f9dcca9..000000000000 --- a/fast_tokenizer/docs/python/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# FastTokenizer Python 库使用教程 - -## 1. 快速安装 - -### 环境依赖 - -- Windows 64位系统 -- Linux x64系统 -- MacOS 10.14+系统(m1芯片的MacOS,需要使用x86_64版本的Anaconda作为python环境方可安装使用) -- Python 3.6 ~ 3.10 - -### 安装 - -```shell -pip install --upgrade fast_tokenizer -``` diff --git a/fast_tokenizer/examples/clip/README.md b/fast_tokenizer/examples/clip/README.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/fast_tokenizer/examples/clip/cpp/CMakeLists.txt b/fast_tokenizer/examples/clip/cpp/CMakeLists.txt deleted file mode 100644 index 271104d7f89c..000000000000 --- a/fast_tokenizer/examples/clip/cpp/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(cpp_fast_tokenizer_demo CXX C) -option(FAST_TOKENIZER_INSTALL_DIR "Path of downloaded fast_tokenizer sdk.") - -# Download clip vocab and merge files -set(CLIP_VOCAB_PATH ${CMAKE_CURRENT_BINARY_DIR}/clip_vocab.json) -set(CLIP_MERGES_PATH ${CMAKE_CURRENT_BINARY_DIR}/clip_merges.txt) - -if (EXISTS ${CLIP_VOCAB_PATH}) - message("The ${CLIP_VOCAB_PATH} exists already.") -else() - file(DOWNLOAD "http://bj.bcebos.com/paddlenlp/models/community/openai/clip-vit-large-patch14/vocab.json" ${CLIP_VOCAB_PATH} SHOW_PROGRESS) - message("Already download the vocab.json of clip to ${CMAKE_CURRENT_BINARY_DIR} for test.") -endif() - -if (EXISTS ${CLIP_MERGES_PATH}) - message("The ${CLIP_MERGES_PATH} exists already.") -else() - file(DOWNLOAD "http://bj.bcebos.com/paddlenlp/models/community/openai/clip-vit-large-patch14/merges.txt" ${CLIP_MERGES_PATH} SHOW_PROGRESS) - message("Already download the merges.txt of clip to ${CMAKE_CURRENT_BINARY_DIR} for test.") -endif() - -# Get FAST_TOKENIZER_INCS and FAST_TOKENIZER_LIBS -include(${FAST_TOKENIZER_INSTALL_DIR}/FastTokenizer.cmake) -include_directories(${FAST_TOKENIZER_INCS}) - -add_executable(demo ${PROJECT_SOURCE_DIR}/demo.cc) -target_link_libraries(demo ${FAST_TOKENIZER_LIBS}) diff --git a/fast_tokenizer/examples/clip/cpp/README.md b/fast_tokenizer/examples/clip/cpp/README.md deleted file mode 100644 index d24c0f7fd65f..000000000000 --- a/fast_tokenizer/examples/clip/cpp/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# ClipFastTokenizer C++ 示例 - -## 1. 快速安装 - -当前版本FastTokenizer C++库支持不同的操作系统以及硬件平台,用户可以根据实际的使用环境,从以下选择合适的预编译包: -|系统|下载地址| -|---|---| -|Linux-x64| [fast_tokenizer-linux-x64-1.0.0.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-linux-x64-1.0.0.tgz) | -|Linux-aarch64| [fast_tokenizer-linux-aarch64-1.0.0.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-linux-aarch64-1.0.0.tgz) | -|Windows| [fast_tokenizer-win-x64-1.0.0.zip](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-win-x64-1.0.0.zip) | -|MacOS-x64| [fast_tokenizer-osx-x86_64-1.0.0.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-osx-x86_64-1.0.0.tgz) | -|MacOS-arm64| [fast_tokenizer-osx-arm64-1.0.0.tgz](https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-osx-arm64-1.0.0.tgz) | - -### 环境依赖 - -#### 系统环境要求 -|系统|版本| -|---|---| -|Linux|Ubuntu 16.04+,CentOS 7+| -|Windows|10| -|MacOS| 11.4+| - - -#### Linux,Mac编译环境要求 -|依赖|版本| -|---|---| -|cmake|>=16.0| -|gcc|>=8.2.0| - -#### Windows编译环境要求 -|依赖|版本| -|---|---| -|cmake|>=16.0| -|VisualStudio|2019| - -## 2. 快速开始 - -以下以Linux平台为例, 介绍如何使用FastTokenizer C++预编译包完成demo示例编译及运行。该示例会生成一个名为`demo`的可执行文件。 - -### 2.1 下载解压 - -```shell -wget -c https://bj.bcebos.com/paddlenlp/fast_tokenizer/fast_tokenizer-linux-x64-1.0.0.tgz - -tar xvfz fast_tokenizer-linux-x64-1.0.0.tgz -# 解压后为fast_tokenizer目录 -``` - -解压后得到fast_tokenizer目录,该目录的结构如下: - -```shell - -fast_tokenizer -|__ commit.log # 编译时的commit id -|__ FastTokenizer.cmake # FastTokenizer CMake文件,定义了头文件目录、动态链接库目录变量 -|__ include # FastTokenizer的头文件目录 -|__ lib # FastTokenizer的动态链接库目录 -|__ third_party # FastTokenizer依赖的第三方库目录 - -``` - -推荐用户直接使用cmake方式引入FastTokenizer库。在CMake引入FastTokenizer时,只需添加一行 `include(FastTokenizer.cmake)`,即可获取FastTokenizer的预定义的CMake变量`FAST_TOKENIZER_INCS`和`FAST_TOKENIZER_LIBS`,分别指定FastTokenizer的头文件目录以及动态链接库目录。 - - -### 2.2 编译 - -示例提供简单的CMakeLists.txt, 用户仅需指定fast_tokenizer包的路径,即可完成编译。 - -```shell - -# 创建编译目录 -mkdir build -cd build - -# 运行cmake,通过指定fast_tokenizer包的路径,构建Makefile -cmake .. -DFAST_TOKENIZER_INSTALL_DIR=/path/to/fast_tokenizer - -# 编译 -make - -``` - -### 2.3 运行 - -```shell -./demo -``` - - -### 2.4 样例输出 - -输出包含原始文本的输入,以及分词后的ids序列结果(含padding)。 - -```shell - -text = "a photo of an astronaut riding a horse on mars" -ids = [49406, 320, 1125, 539, 550, 18376, 6765, 320, 4558, 525, 7496, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407, 49407] - -``` diff --git a/fast_tokenizer/examples/clip/cpp/demo.cc b/fast_tokenizer/examples/clip/cpp/demo.cc deleted file mode 100644 index 0d7983b2ab55..000000000000 --- a/fast_tokenizer/examples/clip/cpp/demo.cc +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include "fast_tokenizer/tokenizers/clip_fast_tokenizer.h" -using namespace paddlenlp; - -template -std::ostream& operator<<(std::ostream& os, const std::vector vec) { - os << "["; - for (int i = 0; i < vec.size(); ++i) { - if (i == 0) { - os << vec[i]; - } else { - os << ", " << vec[i]; - } - } - os << "]"; - return os; -} - -fast_tokenizer::tokenizers_impl::ClipFastTokenizer CreateClipFastTokenizer( - const std::string& vocab_path, - const std::string& merge_path, - uint32_t max_length, - bool pad_to_max_length = true) { - fast_tokenizer::tokenizers_impl::ClipFastTokenizer tokenizer( - vocab_path, merge_path, max_length); - if (pad_to_max_length) { - tokenizer.EnablePadMethod(fast_tokenizer::core::RIGHT, - tokenizer.GetPadTokenId(), - 0, - tokenizer.GetPadToken(), - &max_length, - nullptr); - } - return tokenizer; -} - -int main() { - // 1. Define a clip fast tokenizer - auto tokenizer = CreateClipFastTokenizer("clip_vocab.json", - "clip_merges.txt", - /*max_length = */ 77, - /* pad_to_max_length = */ true); - // 2. Tokenize the input strings - std::vector encodings; - std::vector texts = { - "a photo of an astronaut riding a horse on mars"}; - tokenizer.EncodeBatchStrings(texts, &encodings); - - for (int i = 0; i < texts.size(); ++i) { - std::cout << "text = \"" << texts[i] << "\"" << std::endl; - std::cout << "ids = " << encodings[i].GetIds() << std::endl; - } - - return 0; -} diff --git a/fast_tokenizer/examples/clip/python/README.md b/fast_tokenizer/examples/clip/python/README.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/fast_tokenizer/examples/clip/python/demo.py b/fast_tokenizer/examples/clip/python/demo.py deleted file mode 100644 index 97043fd7ba68..000000000000 --- a/fast_tokenizer/examples/clip/python/demo.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/fast_tokenizer/examples/ernie-3.0/README.md b/fast_tokenizer/examples/ernie-3.0/README.md deleted file mode 100644 index db728533e676..000000000000 --- a/fast_tokenizer/examples/ernie-3.0/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# ErnieFastTokenizer分词示例 - -FastTokenizer库在C++、Python端提供ErnieFastTokenizer接口,用户只需传入模型相应的词表即可调用该接口,完成高效分词操作。该接口底层使用`WordPiece`算法进行分词。针对`WordPiece`算法,FastTokenizer实现了"Fast WordPiece Tokenization"提出的基于`MinMaxMatch`的`FastWordPiece`算法。原有`WordPiece`算法的时间复杂度与序列长度为二次方关系,在对长文本进行分词操作时,时间开销比较大。而`FastWordPiece`算法通过`Aho–Corasick `算法将`WordPiece`算法的时间复杂度降低为与序列长度的线性关系,大大提升了分词效率。`ErnieFastTokenizer`除了支持ERNIE模型的分词以外,还支持其他基于`WordPiece`算法分词的模型,比如`BERT`, `TinyBert`等,详细的模型列表如下: - -## 支持的模型列表 - -- ERNIE -- BERT -- TinyBERT -- ERNIE Gram -- ERNIE ViL - -## 详细分词示例文档 - -[C++ 分词示例](./cpp/README.md) - -[Python 分词示例](./python/README.md) - -## 参考文献 - -- Xinying Song, Alex Salcianuet al. "Fast WordPiece Tokenization", EMNLP, 2021 diff --git a/fast_tokenizer/examples/ernie-3.0/cpp/CMakeLists.txt b/fast_tokenizer/examples/ernie-3.0/cpp/CMakeLists.txt deleted file mode 100644 index b7f349ea036e..000000000000 --- a/fast_tokenizer/examples/ernie-3.0/cpp/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(cpp_fast_tokenizer_demo CXX C) - -option(FAST_TOKENIZER_INSTALL_DIR "Path of downloaded fast_tokenizer sdk.") - -# Download ernie vocab for demo -set(ERNIE_VOCAB_PATH ${CMAKE_CURRENT_BINARY_DIR}/ernie_vocab.txt) -if (EXISTS ${ERNIE_VOCAB_PATH}) - message(STATUS "The ${ERNIE_VOCAB_PATH} exists already.") -else() - file(DOWNLOAD "https://bj.bcebos.com/paddlenlp/models/transformers/ernie/vocab.txt" ${ERNIE_VOCAB_PATH} SHOW_PROGRESS) - message(STATUS "Already download the vocab.txt of ernie to ${CMAKE_CURRENT_BINARY_DIR} for demo.") -endif() - -# Get FAST_TOKENIZER_INCS and FAST_TOKENIZER_LIBS -message(STATUS "The fast_tokenizer install dir: ${FAST_TOKENIZER_INSTALL_DIR}") -include(${FAST_TOKENIZER_INSTALL_DIR}/FastTokenizer.cmake) - -include_directories(${FAST_TOKENIZER_INCS}) - -add_executable(demo ${PROJECT_SOURCE_DIR}/demo.cc) -target_link_libraries(demo ${FAST_TOKENIZER_LIBS}) diff --git a/fast_tokenizer/examples/ernie-3.0/cpp/README.md b/fast_tokenizer/examples/ernie-3.0/cpp/README.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/fast_tokenizer/examples/ernie-3.0/cpp/demo.cc b/fast_tokenizer/examples/ernie-3.0/cpp/demo.cc deleted file mode 100644 index d886791d4d8a..000000000000 --- a/fast_tokenizer/examples/ernie-3.0/cpp/demo.cc +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include "fast_tokenizer/tokenizers/ernie_fast_tokenizer.h" -using namespace paddlenlp; - -int main() { - // 1. Define a ernie fast tokenizer - fast_tokenizer::tokenizers_impl::ErnieFastTokenizer tokenizer( - "ernie_vocab.txt"); - // 2. Tokenize the input strings - // case 1: tokenize a single string - std::cout << "case 1: Tokenize a single string" << std::endl; - fast_tokenizer::core::Encoding encoding; - std::string single_string = - "商赢环球股份有限公司关于延期回复上海证券交易所对" - "公司2017年年度报告的事后审核问询函的公告"; - tokenizer.EncodePairStrings(single_string, &encoding); - std::cout << encoding.DebugString() << std::endl; - - // case 2: tokenize a pair of strings - std::cout << "case 2: Tokenize a pair of strings" << std::endl; - std::string text = "蚂蚁借呗等额还款可以换成先息后本吗"; - std::string text_pair = "借呗有先息到期还本吗"; - - tokenizer.EncodePairStrings(text, text_pair, &encoding); - std::cout << encoding.DebugString() << std::endl; - - // case 3: Tokenize a batch of single strings - std::cout << "case 3: Tokenize a batch of single strings" << std::endl; - std::vector encodings; - std::vector strings_list = { - "通过中介公司买了二手房,首付都付了,现在卖家不想卖了。怎么处理?", - "凌云研发的国产两轮电动车怎么样,有什么惊喜?", - "一辆车的寿命到底多长,最多可以开多久?"}; - tokenizer.EncodeBatchStrings(strings_list, &encodings); - for (auto&& encoding : encodings) { - std::cout << encoding.DebugString() << std::endl; - } - - // case 4: Tokenize a batch of pair strings - std::cout << "case 4: Tokenize a batch of pair strings" << std::endl; - std::vector texts = { - "花呗自动从余额宝扣款,需要我自己设置吗", - "这个蚂蚁花呗能恢复正常用不", - "在经济的另一次转变中,人们发现在低地农场饲养羔羊更具成本效益,部分原因" - "是那里有更丰富、更有营养的牧场,因此湖地农场的利润变得更少。"}; - std::vector text_pairs = { - "支付宝余额会自动还花呗吗", - "我的蚂蚁花呗 怎么用不了", - "人们发现,经济的另一个转变更有营养。"}; - tokenizer.EncodeBatchStrings(texts, text_pairs, &encodings); - for (auto&& encoding : encodings) { - std::cout << encoding.DebugString() << std::endl; - } - return 0; -} \ No newline at end of file diff --git a/fast_tokenizer/examples/ernie-3.0/python/README.md b/fast_tokenizer/examples/ernie-3.0/python/README.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/fast_tokenizer/examples/ernie-3.0/python/demo.py b/fast_tokenizer/examples/ernie-3.0/python/demo.py deleted file mode 100644 index 490604e7bcc7..000000000000 --- a/fast_tokenizer/examples/ernie-3.0/python/demo.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import fast_tokenizer -from fast_tokenizer import ErnieFastTokenizer, models - -fast_tokenizer.set_thread_num(1) -vocab = models.WordPiece.read_file("ernie_vocab.txt") -fast_tokenizer = ErnieFastTokenizer(vocab) -output = fast_tokenizer.encode("我爱中国") -print("ids: ", output.ids) -print("type_ids: ", output.type_ids) -print("tokens: ", output.tokens) -print("offsets: ", output.offsets) -print("attention_mask: ", output.attention_mask) diff --git a/fast_tokenizer/fast_tokenizer/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/CMakeLists.txt deleted file mode 100644 index a4269b2bbd64..000000000000 --- a/fast_tokenizer/fast_tokenizer/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -add_subdirectory(decoders) -add_subdirectory(models) -add_subdirectory(normalizers) -add_subdirectory(pretokenizers) -add_subdirectory(postprocessors) -add_subdirectory(core) -add_subdirectory(utils) -# set the relative path of shared library -if (NOT APPLE AND NOT WIN32) - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-rpath='$ORIGIN'") -endif() - -if (WITH_PYTHON) - add_subdirectory(pybind) - cc_library(core_tokenizers SHARED - SRCS pybind/pybind.cc tokenizers/ernie_fast_tokenizer.cc - DEPS pybind python pybind_normalizers pybind_utils - pybind_pretokenizers pybind_models pybind_decoders - pybind_postprocessors pybind_tokenizers pybind_exception - pybind_core normalizers pretokenizers core models - tokenizer added_vocabulary postprocessors json) - set_target_properties(core_tokenizers PROPERTIES PREFIX "") - if (WIN32) - set_target_properties(core_tokenizers PROPERTIES SUFFIX ".pyd") - else() - set_target_properties(core_tokenizers PROPERTIES SUFFIX ".so") - endif() - - if (APPLE) - SET(CMAKE_INSTALL_RPATH "@loader_path/core_tokenizers.so") - endif() - -else(WITH_PYTHON) - cc_library(core_tokenizers SHARED - SRCS tokenizers/ernie_fast_tokenizer.cc tokenizers/clip_fast_tokenizer.cc - DEPS normalizers pretokenizers models decoders - postprocessors core added_vocabulary tokenizer json) - - if (APPLE) - SET(CMAKE_INSTALL_RPATH "@loader_path/lib/libcore_tokenizers.dylib") - endif() -endif(WITH_PYTHON) - -add_subdirectory(test) diff --git a/fast_tokenizer/fast_tokenizer/core/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/core/CMakeLists.txt deleted file mode 100644 index 2700f66a4401..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -cc_library(added_vocabulary SRCS added_vocabulary.cc DEPS normalizers pretokenizers json) -cc_library(base SRCS base.cc DEPS json) -cc_library(tokenizer SRCS tokenizer.cc DEPS added_vocabulary json decoders trie models postprocessors base) -cc_library(core SRCS encoding.cc DEPS json base) diff --git a/fast_tokenizer/fast_tokenizer/core/added_vocabulary.cc b/fast_tokenizer/fast_tokenizer/core/added_vocabulary.cc deleted file mode 100644 index bdb05fa136b8..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/added_vocabulary.cc +++ /dev/null @@ -1,424 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/core/added_vocabulary.h" -#include "fast_tokenizer/models/model.h" -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "glog/logging.h" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace core { - -inline bool StartWithWord(const std::string& sequence) { - static re2::RE2 pattern("^\\w"); - return re2::RE2::FullMatch(sequence, pattern); -} - -inline bool EndWithWord(const std::string& sequence) { - static re2::RE2 pattern("\\w$"); - return re2::RE2::FullMatch(sequence, pattern); -} - -inline bool StartWithSpace(const std::string& sequence) { - static re2::RE2 pattern("^\\s*"); - return re2::RE2::FullMatch(sequence, pattern); -} - -inline bool EndWithSpace(const std::string& sequence) { - static re2::RE2 pattern("\\s*$"); - return re2::RE2::FullMatch(sequence, pattern); -} - -inline size_t GetEndSpaceIdx(const std::string& sequence) { - static re2::RE2 pattern("\\s*$"); - re2::StringPiece result_str; - pattern.Match( - sequence, 0, sequence.length(), RE2::UNANCHORED, &result_str, 1); - return result_str.data() - sequence.data(); -} - -inline size_t GetStartSpaceIdx(const std::string& sequence) { - static re2::RE2 pattern("^\\s*"); - re2::StringPiece result_str; - pattern.Match( - sequence, 0, sequence.length(), RE2::UNANCHORED, &result_str, 1); - return result_str.data() + result_str.length() - sequence.data(); -} - -inline size_t GetLeftMostSpaceFromEnd(const std::string& sequence) { - if (EndWithSpace(sequence)) { - return GetEndSpaceIdx(sequence); - } - return sequence.length(); -} - -inline size_t GetRightMostSpaceFromStart(const std::string& sequence) { - if (StartWithSpace(sequence)) { - return GetStartSpaceIdx(sequence); - } - return 0; -} - -AddedToken::AddedToken() - : content_(""), - is_single_word_(false), - use_lstrip_(false), - use_rstrip_(false), - use_normalized_(true), - is_special_(false) {} - -AddedToken::AddedToken(const std::string& content, - bool is_special, - bool single_word, - bool lstrip, - bool rstrip) - : content_(content), - is_special_(is_special), - use_normalized_(!is_special), - is_single_word_(single_word), - use_lstrip_(lstrip), - use_rstrip_(rstrip) {} - -std::string AddedToken::GetContent() const { return content_; } - -bool AddedToken::GetIsSpecial() const { return is_special_; } - -bool AddedToken::GetUseNormalized() const { return use_normalized_; } - -void AddedToken::SetIsSingleWord(bool is_single_word) { - is_single_word_ = is_single_word; -} -bool AddedToken::GetUseLStrip() const { return use_lstrip_; } - -bool AddedToken::GetUseRStrip() const { return use_rstrip_; } - -bool AddedToken::GetIsSingleWord() const { return is_single_word_; } - -void AddedToken::SetContent(const std::string& content) { content_ = content; } - -void AddedToken::SetUseLStrip(bool use_lstrip) { use_lstrip_ = use_lstrip; } - -void AddedToken::SetUseRStrip(bool use_rstrip) { use_rstrip_ = use_rstrip; } - -void AddedToken::SetUseNormalized(bool use_normalized) { - use_normalized_ = use_normalized; -} - -void AddedToken::SetIsSpecial(bool is_special) { is_special_ = is_special; } - -bool AddedToken::operator==(const AddedToken& other) const { - return content_ == other.content_; -} - -AddedVocabulary::AddedVocabulary() - : split_trie_({std::make_shared(""), Vocab()}), - split_normalized_trie_({std::make_shared(""), Vocab()}) {} - -size_t AddedVocabulary::GetLen() const { return vocab_.size(); } - -core::Vocab AddedVocabulary::GetVocab() const { return vocab_; } -core::Vocab& AddedVocabulary::GetMutableVocab() { return vocab_; } - -bool AddedVocabulary::TokenToId(const std::string& token, - const models::Model& model, - uint32_t* id) const { - if (vocab_.find(token) != vocab_.end()) { - *id = vocab_.at(token); - return true; - } - return model.TokenToId(token, id); -} - -bool AddedVocabulary::IdToToken(uint32_t id, - const models::Model& model, - std::string* token) const { - if (vocab_reversed_.find(id) != vocab_reversed_.end()) { - *token = vocab_reversed_.at(id).GetContent(); - return true; - } - return model.IdToToken(id, token); -} - -bool AddedVocabulary::IsSpecialToken(const std::string& token) const { - return special_tokens_set_.find(token) != special_tokens_set_.end(); -} - -size_t AddedVocabulary::AddSpecialTokens( - const std::vector& tokens, - const models::Model& model, - const normalizers::Normalizer* normalizers) { - return AddTokens(tokens, model, normalizers); -} - -size_t AddedVocabulary::AddTokens(const std::vector& tokens, - const models::Model& model, - const normalizers::Normalizer* normalizers) { - for (const auto& token : tokens) { - if (token.GetIsSpecial() && !token.GetContent().empty() && - !IsSpecialToken(token.GetContent())) { - special_tokens_.push_back(token); - special_tokens_set_.insert(token.GetContent()); - } - } - int ignored_tokens_num = 0; - for (const auto& token : tokens) { - if (token.GetContent().empty()) { - ignored_tokens_num += 1; - continue; - } - uint32_t id; - if (TokenToId(token.GetContent(), model, &id)) { - ignored_tokens_num += 1; - } else { - uint32_t new_id = model.GetVocabSize() + GetLen(); - vocab_[token.GetContent()] = new_id; - if (special_tokens_set_.count(token.GetContent()) == 0) { - added_tokens_.push_back(token); - } - id = new_id; - } - vocab_reversed_[id] = token; - } - RefreshAddedTokens(model, normalizers); - return tokens.size() - ignored_tokens_num; -} -void AddedVocabulary::RefreshAddedTokens( - const models::Model& model, const normalizers::Normalizer* normalizers) { - using TokenAndId = std::pair; - std::vector normalized, non_normalized; - for (const auto& tokens : {special_tokens_, added_tokens_}) { - for (const auto& token : tokens) { - uint32_t id; - if (TokenToId(token.GetContent(), model, &id)) { - if (token.GetUseNormalized()) { - normalized.push_back({token, id}); - } else { - non_normalized.push_back({token, id}); - } - } - } - } - Vocab ids; - std::vector tokens; - for (const auto& token_ids : non_normalized) { - tokens.push_back(token_ids.first); - ids[token_ids.first.GetContent()] = token_ids.second; - } - // Create a regex pattern - std::string pattern(""); - for (int i = 0; i < tokens.size(); ++i) { - if (i > 0) { - pattern += "|"; - } - std::string pattern_str = ""; - for (const auto& ch : tokens[i].GetContent()) { - if (ch == '[' || ch == ']') { - pattern_str.append(1, '\\'); - } - pattern_str.append(1, ch); - } - pattern += "\(" + pattern_str + "\)"; - } - // Update split_trie_ - split_trie_.first = std::make_shared(pattern); - split_trie_.second = std::move(ids); - Vocab normalized_ids; - std::vector normalized_tokens; - for (const auto& token_ids : normalized) { - normalized_tokens.push_back(token_ids.first); - normalized_ids[token_ids.first.GetContent()] = token_ids.second; - } - - std::string normalized_pattern(""); - for (int i = 0; i < normalized_tokens.size(); ++i) { - normalizers::NormalizedString normalized_content( - normalized_tokens[i].GetContent()); - if (normalizers != nullptr) { - (*normalizers)(&normalized_content); - } - if (i > 0) { - normalized_pattern += "|"; - } - std::string pattern_str = ""; - for (const auto& ch : normalized_content.GetStr()) { - if (ch == '[' || ch == ']') { - pattern_str.append(1, '\\'); - } - pattern_str.append(1, ch); - } - normalized_pattern += "\(" + pattern_str + "\)"; - } - split_normalized_trie_.first = std::make_shared(normalized_pattern); - split_normalized_trie_.second = std::move(normalized_ids); -} - -bool AddedVocabulary::FindMatch(const std::string& sequence, - const MatchSet& pattern, - std::vector* results) const { - if (sequence.empty()) { - return false; - } - std::vector splits; - size_t start = 0; - size_t start_offset = 0; - size_t end = sequence.length(); - re2::StringPiece result_str; - VLOG(6) << "start = " << start << ", end = " << end - << ", sequence = " << sequence - << ", pattern: " << pattern.first->pattern(); - while (pattern.first->Match( - sequence, start, end, RE2::UNANCHORED, &result_str, 1) && - result_str != "") { - VLOG(6) << "result_str: " << result_str << ", " << pattern.first->pattern(); - size_t curr_start = result_str.data() - sequence.data(); - size_t curr_end = curr_start + result_str.length(); - uint32_t id = pattern.second.at(result_str.ToString()); - AddedToken added_tokens = vocab_reversed_.at(id); - VLOG(6) << "start = " << start << ", end = " << end - << ", curr_start = " << curr_start << ", curr_end = " << curr_end; - if (added_tokens.GetIsSingleWord()) { - bool start_space = - (curr_start == 0) || !EndWithWord(sequence.substr(0, curr_start)); - bool stop_space = (curr_end >= sequence.length()) || - !StartWithWord(sequence.substr(curr_end)); - if (!start_space || !stop_space) { - // Discard not single word - start = curr_end; - continue; - } - } - if (added_tokens.GetUseLStrip()) { - auto new_start = GetEndSpaceIdx(sequence.substr(0, curr_start)); - curr_start = std::max(new_start, start_offset); - } - if (added_tokens.GetUseRStrip()) { - curr_end += GetStartSpaceIdx(sequence.substr(curr_end)); - } - if (curr_start > start_offset) { - splits.push_back({0, false, {start_offset, curr_start}}); - } - splits.push_back({id, true, {curr_start, curr_end}}); - start = curr_end; - start_offset = curr_end; - } - if (start != sequence.length()) { - splits.push_back({0, false, {start, sequence.length()}}); - } - *results = std::move(splits); - return true; -} - -bool AddedVocabulary::SplitWithIndices( - const normalizers::NormalizedString& normalized, - const MatchSet& pattern, - std::vector* split_results) const { - std::vector match_results; - bool status = FindMatch(normalized.GetStr(), pattern, &match_results); - for (auto& match_result : match_results) { - normalizers::NormalizedString slice; - auto id = std::get<0>(match_result); - auto is_not_unk = std::get<1>(match_result); - auto offsets = std::get<2>(match_result); - normalized.Slice(offsets, &slice, false); - std::vector tokens; - if (is_not_unk) { - tokens.emplace_back(core::Token{id, slice.GetStr(), {0, slice.GetLen()}}); - } - // use push_back({slice, tokens}) will raise error in windows platform. - split_results->emplace_back(slice, tokens); - } - return status; -} - -void AddedVocabulary::ExtractAndNormalize( - const normalizers::Normalizer* normalizers, - const std::string& sequence, - pretokenizers::PreTokenizedString* pretokenized) const { - pretokenized->SetOriginalStr(sequence); - pretokenized->Split( - [&](int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - this->SplitWithIndices(*normalized, this->split_trie_, string_splits); - }); - pretokenized->Split( - [&](int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - if (normalizers != nullptr) { - (*normalizers)(normalized); - VLOG(6) << "After normalized: " << normalized->GetStr(); - this->SplitWithIndices( - *normalized, this->split_normalized_trie_, string_splits); - } - }); -} - -const std::unordered_map& -AddedVocabulary::GetAddedTokenVocabReversed() const { - return vocab_reversed_; -} - - -void to_json(nlohmann::json& j, const AddedTokenWithId& added_token) { - j = { - {"id", added_token.id_}, - {"content", added_token.added_token_.GetContent()}, - {"single_word", added_token.added_token_.GetIsSingleWord()}, - {"lstrip", added_token.added_token_.GetUseLStrip()}, - {"rstrip", added_token.added_token_.GetUseRStrip()}, - {"normalized", added_token.added_token_.GetUseNormalized()}, - {"special", added_token.added_token_.GetIsSpecial()}, - }; -} - -void from_json(const nlohmann::json& j, AddedTokenWithId& added_token) { - j.at("id").get_to(added_token.id_); - std::string content = j.at("content").get(); - added_token.added_token_.SetContent(content); - - bool single_word = j.at("single_word").get(); - added_token.added_token_.SetIsSingleWord(single_word); - - bool lstrip = j.at("lstrip").get(); - added_token.added_token_.SetUseLStrip(lstrip); - - bool rstrip = j.at("rstrip").get(); - added_token.added_token_.SetUseRStrip(rstrip); - - bool normalized = j.at("normalized").get(); - added_token.added_token_.SetUseNormalized(normalized); - - bool special = j.at("special").get(); - added_token.added_token_.SetIsSpecial(special); -} - -void to_json(nlohmann::json& j, const AddedVocabulary& added_vocab) { - nlohmann::json jarray = nlohmann::json::array(); - for (const auto& vocab_item : added_vocab.vocab_reversed_) { - AddedTokenWithId added_token_with_id; - added_token_with_id.id_ = vocab_item.first; - added_token_with_id.added_token_ = vocab_item.second; - nlohmann::json jo = added_token_with_id; - jarray.emplace_back(jo); - } - j = jarray; -} - -} // namespace core -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/core/added_vocabulary.h b/fast_tokenizer/fast_tokenizer/core/added_vocabulary.h deleted file mode 100644 index a9b26f677818..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/added_vocabulary.h +++ /dev/null @@ -1,154 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include // For shared_ptr -#include -#include - -#include "fast_tokenizer/core/base.h" -#include "nlohmann/json.hpp" - -namespace re2 { -class RE2; -} // namespace re2 - -namespace paddlenlp { -namespace fast_tokenizer { - -namespace normalizers { -class Normalizer; -class NormalizedString; -} // namespace normalizers - -namespace models { -class Model; -} // namespace models - -namespace pretokenizers { -class PreTokenizedString; -struct StringSplit; -} // namespace pretokenizers - -namespace core { - -using MatchSet = std::pair, Vocab>; -using MatchResult = std::tuple; - -bool StartWithWord(const std::string& sequence); -bool EndWithWord(const std::string& sequence); -bool StartWithSpace(const std::string& sequence); -bool EndWithSpace(const std::string& sequence); - -class FASTTOKENIZER_DECL AddedToken { -public: - AddedToken(); - AddedToken(const std::string& content, - bool is_special = false, - bool single_word = false, - bool lstrip = false, - bool rstrip = false); - void SetIsSingleWord(bool is_single_word); - void SetUseLStrip(bool use_lstrip); - void SetUseRStrip(bool use_rstrip); - void SetUseNormalized(bool use_normalized); - void SetContent(const std::string& content); - void SetIsSpecial(bool is_special); - std::string GetContent() const; - bool GetIsSpecial() const; - bool GetUseNormalized() const; - bool GetUseLStrip() const; - bool GetUseRStrip() const; - bool GetIsSingleWord() const; - bool operator==(const AddedToken& other) const; - -private: - std::string content_; - bool is_single_word_; - bool use_lstrip_; - bool use_rstrip_; - bool use_normalized_; - bool is_special_; - friend struct AddedTokenWithId; -}; - -struct FASTTOKENIZER_DECL AddedTokenWithId { - AddedToken added_token_; - uint32_t id_; - friend void to_json(nlohmann::json& j, const AddedTokenWithId& added_token); - friend void from_json(const nlohmann::json& j, AddedTokenWithId& added_token); -}; - -class FASTTOKENIZER_DECL AddedVocabulary { -public: - AddedVocabulary(); - size_t GetLen() const; - core::Vocab& GetMutableVocab(); - core::Vocab GetVocab() const; - bool TokenToId(const std::string& token, - const models::Model& model, - uint32_t* id) const; - bool IdToToken(uint32_t id, - const models::Model& model, - std::string* token) const; - bool IsSpecialToken(const std::string& token) const; - size_t AddSpecialTokens(const std::vector& tokens, - const models::Model& model, - const normalizers::Normalizer* normalizers); - size_t AddTokens(const std::vector& tokens, - const models::Model& model, - const normalizers::Normalizer* normalizers); - void RefreshAddedTokens(const models::Model& model, - const normalizers::Normalizer* normalizers); - bool FindMatch(const std::string& sequence, - const MatchSet& pattern, - std::vector* results) const; - bool SplitWithIndices( - const normalizers::NormalizedString& normalized, - const MatchSet& pattern, - std::vector* split_results) const; - void ExtractAndNormalize( - const normalizers::Normalizer* normalizers, - const std::string& sequence, - pretokenizers::PreTokenizedString* pretokenized) const; - const std::unordered_map& GetAddedTokenVocabReversed() - const; - -private: - core::Vocab vocab_; - std::unordered_map vocab_reversed_; - std::vector added_tokens_; - std::vector special_tokens_; - std::unordered_set special_tokens_set_; - MatchSet split_trie_; - MatchSet split_normalized_trie_; - friend void to_json(nlohmann::json& j, const AddedVocabulary& added_vocab); - friend void from_json(const nlohmann::json& j, AddedVocabulary& added_vocab); -}; - -} // namespace core -} // namespace fast_tokenizer -} // namespace paddlenlp - -namespace std { -template <> -class hash { -public: - size_t operator()( - const paddlenlp::fast_tokenizer::core::AddedToken& added_token) const { - return std::hash()(added_token.GetContent()); - } -}; -} diff --git a/fast_tokenizer/fast_tokenizer/core/base.cc b/fast_tokenizer/fast_tokenizer/core/base.cc deleted file mode 100644 index 2d0bdb23d012..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/base.cc +++ /dev/null @@ -1,53 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/core/base.h" - -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace core { - -static int fast_tokenizer_thread_num = 1; - -void SetThreadNum(int thread_num) { fast_tokenizer_thread_num = thread_num; } - -int GetThreadNum() { return fast_tokenizer_thread_num; } - -void RunMultiThread(std::function func, - size_t batch_size) { - int thread_num = GetThreadNum(); - if (thread_num == 1) { - // Note(zhoushunjie): No need to create threads when - // thread_num equals to 1. - func(0, batch_size); - } else { - std::vector vectorOfThread; - size_t start_index = 0; - size_t step_index = ceil(batch_size / float(thread_num)); - - for (size_t thread_index = 0; thread_index < thread_num; thread_index++) { - vectorOfThread.emplace_back(std::thread(func, start_index, step_index)); - start_index = start_index + step_index; - } - for (size_t thread_index = 0; thread_index < thread_num; thread_index++) { - vectorOfThread[thread_index].join(); - } - } -} - -} // namespace core -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/core/base.h b/fast_tokenizer/fast_tokenizer/core/base.h deleted file mode 100644 index 21af2d912f7c..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/base.h +++ /dev/null @@ -1,378 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace std { -template <> -struct hash> { - size_t operator()(const std::pair& x) const { - size_t h1 = hash()(x.first); - size_t h2 = hash()(x.second); - return h1 ^ (h2 << 1); - } -}; -} - -namespace paddlenlp { -namespace fast_tokenizer { -namespace core { - -enum FASTTOKENIZER_DECL OffsetType { CHAR, BYTE }; -enum FASTTOKENIZER_DECL Direction { LEFT, RIGHT }; -enum FASTTOKENIZER_DECL TruncStrategy { - LONGEST_FIRST, - ONLY_FIRST, - ONLY_SECOND -}; -enum FASTTOKENIZER_DECL PadStrategy { BATCH_LONGEST, FIXED_SIZE }; - -enum FASTTOKENIZER_DECL SplitMode { - REMOVED, - ISOLATED, - MERGED_WITH_PREVIOUS, - MERGED_WITH_NEXT, - CONTIGUOUS -}; - -NLOHMANN_JSON_SERIALIZE_ENUM(OffsetType, - { - {CHAR, "CHAR"}, {BYTE, "BYTE"}, - }); - -NLOHMANN_JSON_SERIALIZE_ENUM(Direction, - { - {LEFT, "LEFT"}, {RIGHT, "RIGHT"}, - }); - -NLOHMANN_JSON_SERIALIZE_ENUM(TruncStrategy, - { - {LONGEST_FIRST, "LONGEST_FIRST"}, - {ONLY_FIRST, "ONLY_FIRST"}, - {ONLY_SECOND, "ONLY_SECOND"}, - }); - - -NLOHMANN_JSON_SERIALIZE_ENUM(PadStrategy, - { - {BATCH_LONGEST, "BATCH_LONGEST"}, - {FIXED_SIZE, "FIXED_SIZE"}, - }); - -struct FASTTOKENIZER_DECL TruncMethod { - Direction direction_; - size_t max_len_; - TruncStrategy strategy_; - size_t stride_; - TruncMethod() - : max_len_(512), - stride_(0), - strategy_(LONGEST_FIRST), - direction_(RIGHT) {} -}; - -struct FASTTOKENIZER_DECL PadMethod { - PadStrategy strategy_; - Direction direction_; - uint32_t pad_id_; - uint32_t pad_token_type_id_; - std::string pad_token_; - uint32_t pad_len_; - uint32_t pad_to_multiple_of_; - - PadMethod() - : strategy_(BATCH_LONGEST), - direction_(RIGHT), - pad_id_(0), - pad_token_type_id_(0), - pad_token_("[PAD]"), - pad_len_(0), - pad_to_multiple_of_(0) {} -}; - -inline void to_json(nlohmann::json& j, const TruncMethod& trunc_method) { - j = { - {"strategy", trunc_method.strategy_}, - {"direction", trunc_method.direction_}, - {"max_len", trunc_method.max_len_}, - {"stride", trunc_method.stride_}, - }; -} - -inline void from_json(const nlohmann::json& j, TruncMethod& trunc_method) { - j["strategy"].get_to(trunc_method.strategy_); - j["direction"].get_to(trunc_method.direction_); - j["max_len"].get_to(trunc_method.max_len_); - j["stride"].get_to(trunc_method.stride_); -} - - -inline void to_json(nlohmann::json& j, const PadMethod& pad_method) { - j = { - {"strategy", pad_method.strategy_}, - {"direction", pad_method.direction_}, - {"pad_id", pad_method.pad_id_}, - {"pad_token_type_id", pad_method.pad_token_type_id_}, - {"pad_token", pad_method.pad_token_}, - {"pad_len", pad_method.pad_len_}, - {"pad_to_multiple_of", pad_method.pad_to_multiple_of_}, - }; -} - -inline void from_json(const nlohmann::json& j, PadMethod& pad_method) { - j["strategy"].get_to(pad_method.strategy_); - j["direction"].get_to(pad_method.direction_); - j["pad_id"].get_to(pad_method.pad_id_); - j["pad_token_type_id"].get_to(pad_method.pad_token_type_id_); - j["pad_token"].get_to(pad_method.pad_token_); - j["pad_len"].get_to(pad_method.pad_len_); - j["pad_to_multiple_of"].get_to(pad_method.pad_to_multiple_of_); -} - -using Offset = std::pair; -using Range = std::pair; -using Vocab = std::unordered_map; -using VocabList = std::vector>; -using VocabReversed = std::unordered_map; -using SortedVocabReversed = std::map; -using Pair = std::pair; -using MergeMap = std::unordered_map>; -using Merges = std::vector>; - -inline void to_json(nlohmann::json& j, - const SortedVocabReversed& sorted_vocab_r) { - j = nlohmann::ordered_json(); - for (const auto& item : sorted_vocab_r) { - j[item.second] = item.first; - } -} - -struct FASTTOKENIZER_DECL Token { - uint32_t id_; - std::string value_; - Offset offset_; - Token() = default; - Token(uint32_t id, const std::string& value, const Offset& offset) - : id_(id), value_(value), offset_(offset) {} -}; - -struct FASTTOKENIZER_DECL Merge { - size_t pos_; - uint32_t rank_; - uint32_t new_id_; - - bool operator==(const Merge& other) const { - return pos_ == other.pos_ && rank_ == other.rank_; - } - bool operator<(const Merge& other) const { - // Used in priority queue - // The queue will output the Merge value - // in ascending order of rank_ - if (rank_ != other.rank_) { - return rank_ > other.rank_; - } - return pos_ > other.pos_; - } -}; - -struct FASTTOKENIZER_DECL Symbol { - uint32_t ch_; // symbol id - int prev_; - int next_; - size_t len_; - - Symbol() = default; - Symbol(uint32_t ch, int prev, int next, size_t len) - : ch_(ch), prev_(prev), next_(next), len_(len) {} - // Merges the current Symbol with the other one. - // In order to update prev/next, we consider Self to be the Symbol on the - // left, - // and other to be the next one on the right. - void MergeWith(const Symbol& other, uint32_t ch) { - ch_ = ch; - next_ = other.next_; - len_ += other.len_; - } -}; - -struct FASTTOKENIZER_DECL BPEWord { - BPEWord() = default; - BPEWord(size_t capacity) { Reserve(capacity); } - void Reserve(size_t capacity) { symbols_.reserve(capacity); } - void Add(uint32_t ch, size_t byte_len) { - int len = symbols_.size(); - int next = -1; - int prev = -1; - if (len >= 1) { - symbols_.back().next_ = len; - prev = len - 1; - } - symbols_.emplace_back(ch, prev, next, byte_len); - } - - void Merge(uint32_t c1, - uint32_t c2, - uint32_t replacement, - std::vector>* changes) { - for (int i = 0; i < symbols_.size(); ++i) { - // Found a byte pair - if (symbols_[i].ch_ == c1 && i + 1 < symbols_.size() && - symbols_[i + 1].ch_ == c2) { - auto& first = symbols_[i]; - auto& second = symbols_[i + 1]; - // If there are other characters before the pair - if (i > 0) { - changes->push_back({{symbols_[i - 1].ch_, first.ch_}, -1}); - changes->push_back({{symbols_[i - 1].ch_, replacement}, 1}); - } - Symbol symbols{ - replacement, first.prev_, second.next_, first.len_ + second.len_}; - symbols_.insert(symbols_.begin() + i, symbols); - symbols_.erase(symbols_.begin() + i + 1, symbols_.begin() + i + 3); - if (i + 1 < symbols_.size()) { - changes->push_back({{second.ch_, symbols_[i + 1].ch_}, -1}); - changes->push_back({{replacement, symbols_[i + 1].ch_}, 1}); - } - } - } - } - - void MergeAll(const MergeMap& merges, const std::vector& dropout) { - std::priority_queue queue; - std::vector skip; - skip.reserve(symbols_.size()); - for (int i = 0; i < symbols_.size() - 1; ++i) { - auto& first = symbols_[i]; - auto& second = symbols_[i + 1]; - if (merges.find({first.ch_, second.ch_}) != merges.end()) { - auto new_merge_info = merges.at({first.ch_, second.ch_}); - core::Merge new_merge{static_cast(i), - new_merge_info.first, - new_merge_info.second}; - queue.push(new_merge); - } - } - std::random_device - rd; // Will be used to obtain a seed for the random number engine - std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with - // rd() - std::uniform_real_distribution distrib(0.0, 1.0); - bool can_skip = (dropout.size() > 0); - while (!queue.empty()) { - // Can't use reference there, because the pop operation will change the - // top value - auto top = queue.top(); - queue.pop(); - if (can_skip && distrib(gen) < dropout[0]) { - // May dropout some merges - skip.push_back(top); - } else { - for (auto& skip_merge : skip) { - queue.push(skip_merge); - } - skip.clear(); - if (symbols_[top.pos_].len_ == 0) { - continue; - } - if (symbols_[top.pos_].next_ == -1) { - continue; - } - size_t next_pos = symbols_[top.pos_].next_; - auto& right = symbols_[next_pos]; - // Make sure we are not processing an expired queue entry - auto target_new_pair = Pair{symbols_[top.pos_].ch_, right.ch_}; - if (merges.find(target_new_pair) == merges.end() || - merges.at(target_new_pair).second != top.new_id_) { - continue; - } - // Otherwise, let's merge - symbols_[top.pos_].MergeWith(right, top.new_id_); - // Tag the right part as removed - symbols_[next_pos].len_ = 0; - // Update `prev` on the new `next` to the current pos - if (right.next_ > -1 && (right.next_ < symbols_.size())) { - symbols_[right.next_].prev_ = top.pos_; - } - // Insert the new pair formed with the previous symbol - auto& current = symbols_[top.pos_]; - if (current.prev_ >= 0) { - auto prev = current.prev_; - auto& prev_symbol = symbols_[prev]; - auto new_pair = Pair{prev_symbol.ch_, current.ch_}; - if (merges.find(new_pair) != merges.end()) { - auto new_merge = merges.at(new_pair); - queue.push({static_cast(current.prev_), - new_merge.first, - new_merge.second}); - } - } - - // Insert the new pair formed with the next symbol - size_t next = current.next_; - if (next < symbols_.size()) { - auto& next_symbol = symbols_[next]; - auto next_pair = Pair{current.ch_, next_symbol.ch_}; - if (merges.find(next_pair) != merges.end()) { - auto new_merge = merges.at(next_pair); - queue.push({top.pos_, new_merge.first, new_merge.second}); - } - } - } - } - symbols_.erase( - std::remove_if(symbols_.begin(), - symbols_.end(), - [](const Symbol& symbol) { return symbol.len_ == 0; }), - symbols_.end()); - } - - void GetChars(std::vector* result) const { - result->reserve(symbols_.size()); - for (const auto& symbol : symbols_) { - result->emplace_back(symbol.ch_); - } - } - - void GetOffset(std::vector* result) const { - result->reserve(symbols_.size()); - uint32_t pos = 0; - for (const auto& symbol : symbols_) { - result->emplace_back(pos, pos + symbol.len_); - pos += symbol.len_; - } - } - - std::vector symbols_; -}; - -FASTTOKENIZER_DECL void SetThreadNum(int thread_num); - -FASTTOKENIZER_DECL int GetThreadNum(); - -FASTTOKENIZER_DECL void RunMultiThread(std::function func, - size_t batch_size); - -} // namespace core -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/core/encoding.cc b/fast_tokenizer/fast_tokenizer/core/encoding.cc deleted file mode 100644 index 379d21df931b..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/encoding.cc +++ /dev/null @@ -1,669 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/core/encoding.h" -#include -#include -#include -#include -#include "glog/logging.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace core { - -Encoding::Encoding(const std::vector& ids, - const std::vector& type_ids, - const std::vector& tokens, - const std::vector& words_idx, - const std::vector& offsets, - const std::vector& special_tokens_mask, - const std::vector& attention_mask, - const std::vector& overflowing, - const std::unordered_map& sequence_ranges) - : ids_(ids), - type_ids_(type_ids), - tokens_(tokens), - words_idx_(words_idx), - offsets_(offsets), - special_tokens_mask_(special_tokens_mask), - attention_mask_(attention_mask), - overflowing_(overflowing), - sequence_ranges_(sequence_ranges) {} -// Move version -Encoding::Encoding(std::vector&& ids, - std::vector&& type_ids, - std::vector&& tokens, - std::vector&& words_idx, - std::vector&& offsets, - std::vector&& special_tokens_mask, - std::vector&& attention_mask, - std::vector&& overflowing, - std::unordered_map&& sequence_ranges) - : ids_(std::move(ids)), - type_ids_(std::move(type_ids)), - tokens_(std::move(tokens)), - words_idx_(std::move(words_idx)), - offsets_(std::move(offsets)), - special_tokens_mask_(std::move(special_tokens_mask)), - attention_mask_(std::move(attention_mask)), - overflowing_(std::move(overflowing)), - sequence_ranges_(std::move(sequence_ranges)) {} - -Encoding::Encoding(uint32_t capacity) { - ids_.reserve(capacity); - type_ids_.reserve(capacity); - tokens_.reserve(capacity); - words_idx_.reserve(capacity); - offsets_.reserve(capacity); - special_tokens_mask_.reserve(capacity); - attention_mask_.reserve(capacity); -} - -Encoding::Encoding(const std::vector& tokens, uint32_t type_id) - : type_ids_(tokens.size(), type_id), - words_idx_(tokens.size(), std::numeric_limits::max()), - attention_mask_(tokens.size(), 1), - special_tokens_mask_(tokens.size(), 0) { - auto length = tokens.size(); - ids_.reserve(length); - offsets_.reserve(length); - tokens_.reserve(length); - for (const auto& token : tokens) { - ids_.push_back(token.id_); - tokens_.push_back(token.value_); - offsets_.push_back(token.offset_); - } -} - -Encoding::Encoding(Encoding&& other) - : ids_(std::move(other.ids_)), - type_ids_(std::move(other.type_ids_)), - tokens_(std::move(other.tokens_)), - words_idx_(std::move(other.words_idx_)), - offsets_(std::move(other.offsets_)), - special_tokens_mask_(std::move(other.special_tokens_mask_)), - attention_mask_(std::move(other.attention_mask_)), - overflowing_(std::move(other.overflowing_)), - sequence_ranges_(std::move(other.sequence_ranges_)) {} - -Encoding& Encoding::operator=(Encoding&& other) { - ids_ = std::move(other.ids_); - type_ids_ = std::move(other.type_ids_); - tokens_ = std::move(other.tokens_); - words_idx_ = std::move(other.words_idx_); - offsets_ = std::move(other.offsets_); - special_tokens_mask_ = std::move(other.special_tokens_mask_); - attention_mask_ = std::move(other.attention_mask_); - overflowing_ = std::move(other.overflowing_); - sequence_ranges_ = std::move(other.sequence_ranges_); - return *this; -} - -bool Encoding::IsEmpty() const { return ids_.empty(); } - -int Encoding::GetLen() const { return ids_.size(); } - -int Encoding::GetNumSequence() const { - if (sequence_ranges_.empty()) { - return 1; - } - return sequence_ranges_.size(); -} - -void Encoding::SetSequenceIds(uint32_t seq_ids) { - sequence_ranges_[seq_ids] = {0, GetLen()}; -} - -const std::vector& Encoding::GetTokens() const { return tokens_; } - -const std::vector& Encoding::GetWordsIdx() const { - return words_idx_; -} - -std::vector& Encoding::GetMutableWordsIdx() { return words_idx_; } - -std::vector Encoding::GetSequenceIds() const { - std::vector sequences(GetLen()); - for (uint32_t seq_id = 0; seq_id < GetNumSequence(); ++seq_id) { - Range range = sequence_ranges_.at(seq_id); - auto seq_len = range.second - range.first; - for (int i = range.first; i < range.second; ++i) { - sequences[i] = seq_id; - } - } - return sequences; -} - -const std::vector& Encoding::GetIds() const { return ids_; } - -const std::vector& Encoding::GetTypeIds() const { return type_ids_; } - -const std::vector& Encoding::GetOffsets() const { return offsets_; } - -std::vector& Encoding::GetMutableOffsets() { return offsets_; } - -const std::vector& Encoding::GetSpecialTokensMask() const { - return special_tokens_mask_; -} - -const std::vector& Encoding::GetAttentionMask() const { - return attention_mask_; -} - -const std::vector& Encoding::GetOverflowing() const { - return overflowing_; -} - -std::vector& Encoding::GetMutableOverflowing() { - return overflowing_; -} - -Range Encoding::GetSequenceRange(uint32_t seq_id) const { - return sequence_ranges_.at(seq_id); -} - -void Encoding::ProcessTokenWithOffsets( - std::function - process_token_fn) { - auto length = GetLen(); - for (int i = 0; i < length; ++i) { - process_token_fn(i, tokens_[i], &offsets_[i]); - } -} - -std::vector Encoding::TokenIdxToSequenceIds( - uint32_t token_idx) const { - std::vector seq_ids; - if (token_idx < GetLen()) { - if (sequence_ranges_.empty()) { - seq_ids.push_back(0); - } else { - for (auto iter = sequence_ranges_.begin(); iter != sequence_ranges_.end(); - ++iter) { - if (token_idx >= iter->second.first && - token_idx < iter->second.second) { - seq_ids.push_back(iter->first); - break; - } - } - } - } - return seq_ids; -} - -std::vector Encoding::WordIdxToTokensIdx(uint32_t word_idx, - uint32_t seq_id) const { - auto seq_range = sequence_ranges_.at(seq_id); - std::vector ranges; - int start = -1; - int end = -1; - for (uint32_t i = seq_range.first; i < seq_range.second; ++i) { - // -1 is the word index of special token - if (words_idx_[i] > word_idx && - words_idx_[i] != std::numeric_limits::max()) { - break; - } - if (words_idx_[i] == word_idx) { - if (start < 0 || i < start) { - start = i; - } - if (end < 0 || i >= end) { - end = i + 1; - } - } - } - if (start >= 0 && end >= 0) { - seq_range.first += start; - seq_range.second += end; - ranges.push_back(seq_range); - } - return ranges; -} - -std::vector Encoding::WordIdxToCharOffsets(uint32_t word_idx, - uint32_t seq_id) const { - std::vector offsets; - std::vector ranges = WordIdxToTokensIdx(word_idx, seq_id); - if (ranges.size() > 0) { - auto start = ranges[0].first; - auto end = ranges[0].second; - if (end > 0) { - offsets.push_back({offsets_[start].first, offsets_[end - 1].second}); - } - } - return offsets; -} - -std::vector> Encoding::TokenIdxToCharOffsets( - uint32_t token_idx) const { - std::vector> results; - auto seq_ids = TokenIdxToSequenceIds(token_idx); - if (seq_ids.size() > 0) { - results.push_back({seq_ids[0], offsets_[token_idx]}); - } - return results; -} - -std::vector> Encoding::TokenIdxToWordIdx( - uint32_t token_idx) const { - std::vector> results; - auto seq_ids = TokenIdxToSequenceIds(token_idx); - if (seq_ids.size() > 0) { - results.push_back({seq_ids[0], words_idx_[token_idx]}); - } - return results; -} - -std::vector Encoding::CharOffsetsToTokenIdx(uint32_t char_pos, - uint32_t seq_id) const { - std::vector token_idx; - auto seq_range = sequence_ranges_.at(seq_id); - for (int i = seq_range.first; i < seq_range.second; ++i) { - if (char_pos >= offsets_[i].first && char_pos < offsets_[i].second) { - token_idx.push_back(i); - break; - } - } - return token_idx; -} - -std::vector Encoding::CharOffsetsToWordIdx(uint32_t char_pos, - uint32_t seq_id) const { - std::vector token_idx = CharOffsetsToTokenIdx(char_pos, seq_id); - std::vector word_idx; - if (token_idx.size() > 0) { - auto words_idx = TokenIdxToWordIdx(token_idx[0]); - if (words_idx.size() > 0) { - word_idx.push_back(words_idx[0].second); - } - } - return word_idx; -} - -void Encoding::Truncate(size_t max_len, size_t stride, Direction direction) { - size_t encoding_len = ids_.size(); - if (max_len < encoding_len) { - if (max_len == 0) { - *this = Encoding(0); - overflowing_.push_back(*this); - return; - } - assert(stride < max_len); - sequence_ranges_.clear(); - - size_t step_len = max_len - stride; - bool found_end = false; - std::vector part_ranges; - // Get PartRanges - if (direction == RIGHT) { - for (size_t start = 0; start < encoding_len && !found_end; - start += step_len) { - size_t stop = std::min(start + max_len, encoding_len); - found_end = (stop == encoding_len); - part_ranges.push_back({start, stop}); - } - } else { - for (size_t i = 0; i < encoding_len; i += step_len) { - size_t stop = encoding_len - i; - size_t start = (stop < max_len) ? 0 : stop - max_len; - if (start < stop && !found_end) { - found_end = (start == 0); - part_ranges.push_back({start, stop}); - } else { - break; - } - } - } - // Create new encoding - auto new_encoding_len = part_ranges[0].second - part_ranges[0].first; - Encoding new_encoding( - std::vector(ids_.begin(), ids_.begin() + new_encoding_len), - std::vector(type_ids_.begin(), - type_ids_.begin() + new_encoding_len), - std::vector(tokens_.begin(), - tokens_.begin() + new_encoding_len), - std::vector(words_idx_.begin(), - words_idx_.begin() + new_encoding_len), - std::vector(offsets_.begin(), - offsets_.begin() + new_encoding_len), - std::vector(special_tokens_mask_.begin(), - special_tokens_mask_.begin() + new_encoding_len), - std::vector(attention_mask_.begin(), - attention_mask_.begin() + new_encoding_len), - std::vector(), - std::unordered_map()); - // Set overflowing - for (size_t i = 1; i < part_ranges.size() - 1; ++i) { - auto start = part_ranges[i].first; - auto end = part_ranges[i].second; - new_encoding.overflowing_.emplace_back(Encoding( - std::vector(ids_.begin() + start, ids_.begin() + end), - std::vector(type_ids_.begin() + start, - type_ids_.begin() + end), - std::vector(tokens_.begin() + start, - tokens_.begin() + end), - std::vector(words_idx_.begin() + start, - words_idx_.begin() + end), - std::vector(offsets_.begin() + start, offsets_.begin() + end), - std::vector(special_tokens_mask_.begin() + start, - special_tokens_mask_.begin() + end), - std::vector(attention_mask_.begin() + start, - attention_mask_.begin() + end), - std::vector(), - std::unordered_map())); - } - *this = std::move(new_encoding); - } -} - - -void Encoding::MergeWith(const Encoding& pair, bool growing_offsets) { - std::vector overflowings; - - for (const auto& this_o : overflowing_) { - auto n_encoding = this_o; - n_encoding.MergeWith(pair, growing_offsets); - overflowings.emplace_back(n_encoding); - for (const auto& pair_o : pair.overflowing_) { - auto n_encoding = this_o; - n_encoding.MergeWith(pair_o, growing_offsets); - overflowings.emplace_back(n_encoding); - } - } - for (const auto& pair_o : pair.overflowing_) { - auto n_encoding = *this; - n_encoding.MergeWith(pair_o, growing_offsets); - overflowings.emplace_back(n_encoding); - } - - auto orignal_len = GetLen(); - for (const auto& pair_seq_range : pair.sequence_ranges_) { - sequence_ranges_.insert({pair_seq_range.first, - {pair_seq_range.second.first + orignal_len, - pair_seq_range.second.second + orignal_len}}); - } -#define EXTEND_VECTOR(member) \ - member.insert(member.end(), pair.member.begin(), pair.member.end()) - EXTEND_VECTOR(ids_); - EXTEND_VECTOR(type_ids_); - EXTEND_VECTOR(tokens_); - EXTEND_VECTOR(words_idx_); - EXTEND_VECTOR(special_tokens_mask_); - EXTEND_VECTOR(attention_mask_); -#undef EXTEND_VECTOR - // Setting offet - uint32_t starting_offset = 0; - if (growing_offsets && offsets_.size() > 0) { - starting_offset = offsets_.back().second; - } - for (const auto& pair_offset : pair.offsets_) { - offsets_.push_back({pair_offset.first + starting_offset, - pair_offset.second + starting_offset}); - } - - overflowing_ = std::move(overflowings); -} - -void Encoding::Pad(uint32_t target_length, - uint32_t pad_id, - uint32_t pad_type_id, - const std::string& pad_token, - Direction direction) { - for (auto& overflowing : overflowing_) { - overflowing.Pad(target_length, pad_id, pad_type_id, pad_token, direction); - } - // Need to be padded in this situation - if (GetLen() < target_length) { - auto pad_len = target_length - GetLen(); - if (direction == LEFT) { - ids_.insert(ids_.begin(), pad_len, pad_id); - type_ids_.insert(type_ids_.begin(), pad_len, pad_type_id); - tokens_.insert(tokens_.begin(), pad_len, pad_token); - words_idx_.insert( - words_idx_.begin(), pad_len, std::numeric_limits::max()); - attention_mask_.insert(attention_mask_.begin(), pad_len, 0); - special_tokens_mask_.insert(special_tokens_mask_.begin(), pad_len, 1); - offsets_.insert(offsets_.begin(), pad_len, {0, 0}); - } else { - ids_.insert(ids_.end(), pad_len, pad_id); - type_ids_.insert(type_ids_.end(), pad_len, pad_type_id); - tokens_.insert(tokens_.end(), pad_len, pad_token); - words_idx_.insert( - words_idx_.end(), pad_len, std::numeric_limits::max()); - attention_mask_.insert(attention_mask_.end(), pad_len, 0); - special_tokens_mask_.insert(special_tokens_mask_.end(), pad_len, 1); - offsets_.insert(offsets_.end(), pad_len, {0, 0}); - } - } -} - -// Static method -Encoding Encoding::Merge(const std::vector& encodings, - bool growing_offsets) { - Encoding merged_encoding; - for (auto& encoding : encodings) { - merged_encoding.MergeWith(encoding, growing_offsets); - } - return merged_encoding; -} - -void Encoding::SetTypeIds(const std::vector& type_ids) { - type_ids_ = type_ids; -} - -bool Encoding::operator==(const Encoding& other) const { - if (overflowing_.size() != other.overflowing_.size()) { - return false; - } - for (int i = 0; i < overflowing_.size(); ++i) { - if (!(overflowing_[i] == other.overflowing_[i])) { - return false; - } - } - return ids_ == other.ids_ && type_ids_ == other.type_ids_ && - tokens_ == other.tokens_ && words_idx_ == other.words_idx_ && - offsets_ == other.offsets_ && - special_tokens_mask_ == other.special_tokens_mask_ && - attention_mask_ == other.attention_mask_ && - sequence_ranges_ == other.sequence_ranges_; -} - -std::string Encoding::DebugString() const { - std::ostringstream oss; - oss << "The Encoding content: \n"; - oss << "ids: "; - for (int i = 0; i < ids_.size(); ++i) { - oss << ids_[i]; - if (i < ids_.size() - 1) { - oss << ", "; - } - } - oss << "\n"; - - oss << "type_ids: "; - for (int i = 0; i < type_ids_.size(); ++i) { - oss << type_ids_[i]; - if (i < type_ids_.size() - 1) { - oss << ", "; - } - } - oss << "\n"; - - oss << "tokens: "; - for (int i = 0; i < tokens_.size(); ++i) { - oss << tokens_[i]; - if (i < tokens_.size() - 1) { - oss << ", "; - } - } - oss << "\n"; - - oss << "offsets: "; - for (int i = 0; i < offsets_.size(); ++i) { - oss << "(" << offsets_[i].first << ", " << offsets_[i].second << ")"; - if (i < offsets_.size() - 1) { - oss << ", "; - } - } - oss << "\n"; - - oss << "special_tokens_mask: "; - for (int i = 0; i < special_tokens_mask_.size(); ++i) { - oss << special_tokens_mask_[i]; - if (i < special_tokens_mask_.size() - 1) { - oss << ", "; - } - } - oss << "\n"; - - oss << "attention_mask: "; - for (int i = 0; i < attention_mask_.size(); ++i) { - oss << attention_mask_[i]; - if (i < attention_mask_.size() - 1) { - oss << ", "; - } - } - oss << "\n"; - - oss << "sequence_ranges: "; - for (auto iter = sequence_ranges_.begin(); iter != sequence_ranges_.end(); - ++iter) { - oss << "{" << iter->first << " : (" << iter->second.first << ", " - << iter->second.second << ") }, "; - } - return oss.str(); -} - - -bool TruncateEncodings(Encoding* encoding, - Encoding* pair_encoding, - const TruncMethod& method) { - if (method.max_len_ == 0) { - encoding->Truncate(0, method.stride_, method.direction_); - if (pair_encoding != nullptr) { - pair_encoding->Truncate(0, method.stride_, method.direction_); - } - return true; - } - size_t total_length = encoding->GetIds().size(); - if (pair_encoding != nullptr) { - total_length += pair_encoding->GetIds().size(); - } - if (total_length <= method.max_len_) { - return true; - } - auto num_of_removed_ids = total_length - method.max_len_; - - if (method.strategy_ == TruncStrategy::LONGEST_FIRST) { - if (pair_encoding == nullptr) { - encoding->Truncate(method.max_len_, method.stride_, method.direction_); - } else { - auto encoding_len = encoding->GetIds().size(); - auto pair_encoding_len = pair_encoding->GetIds().size(); - bool has_swapped = false; - if (encoding_len > pair_encoding_len) { - std::swap(encoding_len, pair_encoding_len); - has_swapped = true; - } - if (encoding_len > method.max_len_) { - pair_encoding_len = encoding_len; - } else { - pair_encoding_len = - std::max(method.max_len_ - encoding_len, encoding_len); - } - if (pair_encoding_len + encoding_len > method.max_len_) { - // In this case, make sure the encoding_len is larger than - // pair_encoding_len - encoding_len = method.max_len_ / 2; - pair_encoding_len = encoding_len + method.max_len_ % 2; - } - if (has_swapped) { - std::swap(encoding_len, pair_encoding_len); - } - encoding->Truncate(encoding_len, method.stride_, method.direction_); - pair_encoding->Truncate( - pair_encoding_len, method.stride_, method.direction_); - } - } else { - // TruncStrategy::ONLY_FIRST or TruncStrategy::ONLY_SECOND - Encoding* result = nullptr; - if (method.strategy_ == TruncStrategy::ONLY_FIRST) { - result = encoding; - } else if (method.strategy_ == TruncStrategy::ONLY_SECOND) { - if (pair_encoding == nullptr) { - // Can't truncate the pair text when it doesn't exist - return false; - } - result = pair_encoding; - } - if (result->GetIds().size() > num_of_removed_ids) { - result->Truncate(result->GetIds().size() - num_of_removed_ids, - method.stride_, - method.direction_); - } else { - // Target sequence is too short to be truncated. - return false; - } - } - return true; -} - -void MultiThreadPadEncodings(std::vector* encodings, - const PadMethod& method, - size_t pad_length, - size_t start_index, - size_t step_index) { - auto batch_size = encodings->size(); - size_t end_index = start_index + step_index; - if (end_index > batch_size) end_index = batch_size; - for (size_t i = start_index; i < end_index; ++i) { - auto& encoding = (*encodings)[i]; - encoding.Pad(pad_length, - method.pad_id_, - method.pad_token_type_id_, - method.pad_token_, - method.direction_); - } -} -void PadEncodings(std::vector* encodings, const PadMethod& method) { - if (encodings == nullptr || encodings->empty()) { - return; - } - size_t pad_length = 0; - if (method.strategy_ == PadStrategy::BATCH_LONGEST) { - for (const auto& encoding : *encodings) { - pad_length = std::max(encoding.GetIds().size(), pad_length); - } - } else { - pad_length = method.pad_len_; - } - if (method.pad_to_multiple_of_ > 0 && - pad_length % method.pad_to_multiple_of_) { - pad_length += pad_length - pad_length % method.pad_to_multiple_of_; - } - auto batch_size = encodings->size(); - auto func = std::bind(&MultiThreadPadEncodings, - encodings, - std::ref(method), - pad_length, - std::placeholders::_1, - std::placeholders::_2); - RunMultiThread(func, batch_size); -} - - -} // namespace core -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/core/encoding.h b/fast_tokenizer/fast_tokenizer/core/encoding.h deleted file mode 100644 index 5a9d3a41b714..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/encoding.h +++ /dev/null @@ -1,135 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include -#include -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/utils/utils.h" - -#include -#include -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace core { - -class FASTTOKENIZER_DECL Encoding { -public: - Encoding() = default; - Encoding(const std::vector& ids, - const std::vector& type_ids, - const std::vector& tokens, - const std::vector& words_idx, - const std::vector& offsets, - const std::vector& special_tokens_mask, - const std::vector& attention_mask, - const std::vector& overflowing, - const std::unordered_map& sequence_ranges); - // Move version - Encoding(std::vector&& ids, - std::vector&& type_ids, - std::vector&& tokens, - std::vector&& words_idx, - std::vector&& offsets, - std::vector&& special_tokens_mask, - std::vector&& attention_mask, - std::vector&& overflowing, - std::unordered_map&& sequence_ranges); - Encoding(uint32_t size); - Encoding(const std::vector& tokens, uint32_t type_id); - - Encoding(Encoding&&); - Encoding(const Encoding&) = default; - Encoding& operator=(Encoding&&); - Encoding& operator=(const Encoding&) = default; - - bool IsEmpty() const; - void SetSequenceIds(uint32_t seq_ids); - - // Getter - int GetLen() const; - int GetNumSequence() const; - const std::vector& GetTokens() const; - const std::vector& GetWordsIdx() const; - std::vector& GetMutableWordsIdx(); - std::vector GetSequenceIds() const; - const std::vector& GetIds() const; - const std::vector& GetTypeIds() const; - const std::vector& GetOffsets() const; - std::vector& GetMutableOffsets(); - const std::vector& GetSpecialTokensMask() const; - const std::vector& GetAttentionMask() const; - const std::vector& GetOverflowing() const; - std::vector& GetMutableOverflowing(); - Range GetSequenceRange(uint32_t seq_id) const; - - void ProcessTokenWithOffsets( - std::function - process_token_fn); - - // token_idx: The index of token in the sequence - std::vector TokenIdxToSequenceIds(uint32_t token_idx) const; - std::vector WordIdxToTokensIdx(uint32_t word_idx, - uint32_t seq_id) const; - std::vector WordIdxToCharOffsets(uint32_t word_idx, - uint32_t seq_id) const; - std::vector> TokenIdxToCharOffsets( - uint32_t token_idx) const; - std::vector> TokenIdxToWordIdx( - uint32_t token_idx) const; - std::vector CharOffsetsToTokenIdx(uint32_t char_pos, - uint32_t seq_id) const; - std::vector CharOffsetsToWordIdx(uint32_t char_pos, - uint32_t seq_id) const; - void Truncate(size_t max_len, size_t stride, Direction direction); - void MergeWith(const Encoding& pair, bool growing_offsets); - void Pad(uint32_t target_length, - uint32_t pad_id, - uint32_t pad_type_id, - const std::string& pad_token, - Direction direction); - // Static method - static Encoding Merge(const std::vector& encodings, - bool growing_offsets); - std::string DebugString() const; - void SetTypeIds(const std::vector& type_ids); - bool operator==(const Encoding& other) const; - -private: - std::vector ids_; - std::vector type_ids_; - std::vector tokens_; - std::vector words_idx_; - std::vector offsets_; - std::vector special_tokens_mask_; - std::vector attention_mask_; - std::vector overflowing_; - std::unordered_map sequence_ranges_; -}; - -bool FASTTOKENIZER_DECL TruncateEncodings(Encoding* encoding, - Encoding* pair_encoding, - const TruncMethod& method); -void FASTTOKENIZER_DECL PadEncodings(std::vector* encoding, - const PadMethod& method); - -} // namespace core -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/core/tokenizer.cc b/fast_tokenizer/fast_tokenizer/core/tokenizer.cc deleted file mode 100644 index 2b907b56936e..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/tokenizer.cc +++ /dev/null @@ -1,876 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/core/tokenizer.h" - -#include - -#include "fast_tokenizer/core/added_vocabulary.h" -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/decoders/decoders.h" -#include "fast_tokenizer/models/models.h" -#include "fast_tokenizer/normalizers/normalizers.h" -#include "fast_tokenizer/postprocessors/postprocessors.h" -#include "fast_tokenizer/pretokenizers/pretokenizers.h" -#include "glog/logging.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace core { - -normalizers::Normalizer* Tokenizer::GetNormalizerPtr() const { - return normalizer_.get(); -} - -void Tokenizer::ReleaseNormaizer() { normalizer_ = nullptr; } - -pretokenizers::PreTokenizer* Tokenizer::GetPreTokenizer() const { - return pretokenizer_.get(); -} - -void Tokenizer::ReleasePreTokenizer() { pretokenizer_ = nullptr; } - -void Tokenizer::SetTruncMethod(const TruncMethod& trunc_method) { - trunc_method_ = trunc_method; -} - -void Tokenizer::EnableTruncMethod(size_t max_len, - size_t stride, - Direction direction, - TruncStrategy strategy) { - use_truncation_ = true; - trunc_method_.direction_ = direction; - trunc_method_.max_len_ = max_len; - trunc_method_.strategy_ = strategy; - trunc_method_.stride_ = stride; -} - -void Tokenizer::DisableTruncMethod() { use_truncation_ = false; } - -TruncMethod Tokenizer::GetTruncMethod() const { return trunc_method_; } - -void Tokenizer::SetPadMethod(const PadMethod& pad_method) { - pad_method_ = pad_method; -} - -void Tokenizer::EnablePadMethod(Direction direction, - uint32_t pad_id, - uint32_t pad_type_id, - const std::string& pad_token, - uint32_t* length, - uint32_t* pad_to_multiple_of) { - use_padding_ = true; - pad_method_.direction_ = direction; - pad_method_.pad_id_ = pad_id; - pad_method_.pad_token_type_id_ = pad_type_id; - pad_method_.pad_token_ = pad_token; - if (length != nullptr) { - pad_method_.pad_len_ = *length; - pad_method_.strategy_ = PadStrategy::FIXED_SIZE; - } else { - pad_method_.strategy_ = PadStrategy::BATCH_LONGEST; - } - if (pad_to_multiple_of != nullptr) { - pad_method_.pad_to_multiple_of_ = *pad_to_multiple_of; - } else { - pad_method_.pad_to_multiple_of_ = 0; - } -} -void Tokenizer::DisablePadMethod() { use_padding_ = false; } - -PadMethod Tokenizer::GetPadMethod() const { return pad_method_; } - -models::Model* Tokenizer::GetModelPtr() const { return model_.get(); } - -void Tokenizer::ReleasePostProcessor() { post_processor_ = nullptr; } - -postprocessors::PostProcessor* Tokenizer::GetPostProcessorPtr() const { - return post_processor_.get(); -} - -void Tokenizer::ReleaseDecoder() { decoder_ = nullptr; } - -decoders::Decoder* Tokenizer::GetDecoderPtr() const { return decoder_.get(); } - -Vocab Tokenizer::GetVocab(bool with_added_vocabulary) const { - auto vocab = model_->GetVocab(); - auto added_vocab = added_vocabulary_.GetVocab(); - if (with_added_vocabulary) { - for (const auto& vocab_item : added_vocab) { - vocab.insert(vocab_item); - } - } - return vocab; -} - -size_t Tokenizer::GetVocabSize(bool with_added_vocabulary) const { - size_t vocab_size = model_->GetVocabSize(); - if (with_added_vocabulary) { - vocab_size += added_vocabulary_.GetLen(); - } - return vocab_size; -} - -size_t Tokenizer::AddTokens(const std::vector& tokens) { - return added_vocabulary_.AddTokens(tokens, *model_, normalizer_.get()); -} - -size_t Tokenizer::AddSpecialTokens(const std::vector& tokens) { - return added_vocabulary_.AddSpecialTokens(tokens, *model_, normalizer_.get()); -} - -bool Tokenizer::TokenToId(const std::string& token, uint32_t* id) const { - return added_vocabulary_.TokenToId(token, *model_, id); -} - -bool Tokenizer::IdToToken(uint32_t id, std::string* token) const { - return added_vocabulary_.IdToToken(id, *model_, token); -} - -bool Tokenizer::DoTokenize(pretokenizers::PreTokenizedString* pretokenized, - uint32_t type_id, - const std::vector& word_idx, - OffsetType offset_type, - Encoding* encoding) const { - pretokenized->Tokenize([&](normalizers::NormalizedString* normalized) { - return this->GetModelPtr()->Tokenize(normalized->GetStr()); - }); - return pretokenized->TransformToEncoding( - word_idx, type_id, offset_type, encoding); -} - -bool Tokenizer::DoPreTokenize( - pretokenizers::PreTokenizedString* pretokenized) const { - if (pretokenizer_ != nullptr) { - (*pretokenizer_)(pretokenized); - } - return true; -} - -struct InputStringVisitor { - InputStringVisitor(const Tokenizer* tokenizer, - uint32_t type_id, - OffsetType offset_type, - Encoding* encodings) - : tokenizer_(tokenizer), - type_id_(type_id), - offset_type_(offset_type), - encodings_(encodings) {} - void operator()(const std::vector& pretokenized_texts) const { - tokenizer_->EncodeSingleText( - pretokenized_texts, type_id_, offset_type_, encodings_); - } - - void operator()(const std::string& raw_text) const { - tokenizer_->EncodeSingleText(raw_text, type_id_, offset_type_, encodings_); - } - const Tokenizer* tokenizer_; - uint32_t type_id_; - OffsetType offset_type_; - Encoding* encodings_; -}; - -void Tokenizer::EncodeSingleString(const InputString& input_string, - uint32_t type_id, - OffsetType offset_type, - Encoding* encodings) const { - paddlenlp::visit(InputStringVisitor(this, type_id, offset_type, encodings), - input_string); -} - -void Tokenizer::PostProcess(Encoding* encoding, - Encoding* pair_encoding, - bool add_special_tokens, - Encoding* result_encoding) const { - // 1. Trunc - if (use_truncation_) { - auto added_tokens_num = 0; - if (post_processor_ != nullptr) { - added_tokens_num = - post_processor_->AddedTokensNum(pair_encoding != nullptr); - } - if (add_special_tokens && added_tokens_num > 0) { - auto trunc_method = trunc_method_; - trunc_method.max_len_ -= added_tokens_num; - TruncateEncodings(encoding, pair_encoding, trunc_method); - } else { - TruncateEncodings(encoding, pair_encoding, trunc_method_); - } - } - // 2. Post process - if (post_processor_ == nullptr) { - postprocessors::PostProcessor::DefaultProcess( - encoding, pair_encoding, result_encoding); - } else { - (*post_processor_)( - encoding, pair_encoding, add_special_tokens, result_encoding); - } - // 3. Pad - if (use_padding_) { - std::vector encodings; - encodings.push_back(*result_encoding); - PadEncodings(&encodings, pad_method_); - } -} - -void Tokenizer::EncodePairStrings(const EncodeInput& encode_input, - Encoding* encodings, - bool add_special_tokens) const { - Encoding encoding; - if (encode_input.type() == typeid(InputString)) { - const auto& input_string = paddlenlp::get(encode_input); - EncodeSingleString(input_string, 0, OffsetType::CHAR, &encoding); - PostProcess(&encoding, nullptr, add_special_tokens, encodings); - } else { - Encoding pair_encoding; - const auto& input_string_pair = - paddlenlp::get>(encode_input); - EncodeSingleString(input_string_pair.first, 0, OffsetType::CHAR, &encoding); - EncodeSingleString( - input_string_pair.second, 1, OffsetType::CHAR, &pair_encoding); - PostProcess(&encoding, &pair_encoding, add_special_tokens, encodings); - } -} - -void Tokenizer::EncodePairStrings(const std::string& text, - const std::string& text_pair, - Encoding* encodings, - bool add_special_tokens) const { - Encoding encoding, pair_encoding; - EncodeSingleString(text, 0, OffsetType::CHAR, &encoding); - EncodeSingleString(text_pair, 1, OffsetType::CHAR, &pair_encoding); - PostProcess(&encoding, &pair_encoding, add_special_tokens, encodings); -} - -void Tokenizer::MultiThreadEncodeBatchStrings( - const std::vector& texts, - const std::vector& text_pairs, - std::vector* encodings, - bool add_special_tokens, - size_t start_index, - size_t step_index) const { - if (texts.size() != text_pairs.size()) { - throw std::runtime_error( - "The size of text must equal to the size of text_pair"); - } - auto batch_size = texts.size(); - size_t end_index = start_index + step_index; - if (end_index > batch_size) end_index = batch_size; - for (size_t i = start_index; i < end_index; ++i) { - EncodePairStrings( - texts[i], text_pairs[i], &(*encodings)[i], add_special_tokens); - } -} - -void Tokenizer::MultiThreadEncodeBatchStrings( - const std::vector& batch_encode_input, - std::vector* encodings, - bool add_special_tokens, - size_t start_index, - size_t step_index) const { - auto batch_size = batch_encode_input.size(); - size_t end_index = start_index + step_index; - if (end_index > batch_size) end_index = batch_size; - for (size_t i = start_index; i < end_index; ++i) { - EncodePairStrings( - batch_encode_input[i], &(*encodings)[i], add_special_tokens); - } -} - -void Tokenizer::MultiThreadEncodeBatchStrings( - const std::vector& texts, - std::vector* encodings, - bool add_special_tokens, - size_t start_index, - size_t step_index) const { - auto batch_size = texts.size(); - size_t end_index = start_index + step_index; - if (end_index > batch_size) end_index = batch_size; - for (size_t i = start_index; i < end_index; ++i) { - EncodePairStrings(texts[i], &(*encodings)[i], add_special_tokens); - } -} - -void Tokenizer::EncodeBatchStrings( - const std::vector& batch_encode_input, - std::vector* encodings, - bool add_special_tokens) const { - auto batch_size = batch_encode_input.size(); - encodings->resize(batch_size); - auto func = [&](size_t start_index, size_t step_index) { - MultiThreadEncodeBatchStrings(batch_encode_input, - encodings, - add_special_tokens, - start_index, - step_index); - }; - RunMultiThread(func, batch_size); - - if (use_padding_) { - PadEncodings(encodings, pad_method_); - } -} - -void Tokenizer::EncodeBatchStrings(const std::vector& texts, - std::vector* encodings, - bool add_special_tokens) const { - auto batch_size = texts.size(); - encodings->resize(batch_size); - auto func = [&](size_t start_index, size_t step_index) { - MultiThreadEncodeBatchStrings( - texts, encodings, add_special_tokens, start_index, step_index); - }; - RunMultiThread(func, batch_size); - - if (use_padding_) { - PadEncodings(encodings, pad_method_); - } -} - -void Tokenizer::EncodeBatchStrings(const std::vector& texts, - const std::vector& text_pairs, - std::vector* encodings, - bool add_special_tokens) const { - auto batch_size = texts.size(); - encodings->resize(batch_size); - auto func = [&](size_t start_index, size_t step_index) { - MultiThreadEncodeBatchStrings(texts, - text_pairs, - encodings, - add_special_tokens, - start_index, - step_index); - }; - RunMultiThread(func, batch_size); - - if (use_padding_) { - PadEncodings(encodings, pad_method_); - } -} - -void Tokenizer::EncodeSingleText( - const std::vector& pretokenized_texts, - uint32_t type_id, - OffsetType offset_type, - Encoding* encoding) const { - std::vector encodings; - for (uint32_t i = 0; i < pretokenized_texts.size(); ++i) { - encodings.emplace_back( - EncodeTextToEncoding({i}, type_id, offset_type, pretokenized_texts[i])); - } - *encoding = Encoding::Merge(encodings, false); -} - -void Tokenizer::EncodeSingleText(const std::string& raw_text, - uint32_t type_id, - OffsetType offset_type, - Encoding* encodings) const { - *encodings = EncodeTextToEncoding({}, type_id, offset_type, raw_text); -} - -Encoding Tokenizer::EncodeTextToEncoding(const std::vector& word_idx, - uint32_t type_id, - OffsetType offset_type, - const std::string& text) const { - pretokenizers::PreTokenizedString pretokenized; - added_vocabulary_.ExtractAndNormalize(normalizer_.get(), text, &pretokenized); - DoPreTokenize(&pretokenized); - Encoding encoding; - DoTokenize(&pretokenized, type_id, word_idx, offset_type, &encoding); - return encoding; -} - -const AddedVocabulary& Tokenizer::GetAddedVocabulary() const { - return added_vocabulary_; -} - -void Tokenizer::Save(const std::string& path, bool pretty) const { - std::string json_str; - ToJsonStr(&json_str, pretty); - std::ofstream fout(path); - fout << json_str; -} - -void Tokenizer::ToJsonStr(std::string* json_str, bool pretty) const { - int indent = -1; - if (pretty) { - indent = 2; - } - nlohmann::json j = *this; - *json_str = j.dump(indent); -} - -Tokenizer Tokenizer::LoadFromFile(const std::string& json_path) { - std::ifstream fin(json_path); - nlohmann::json j; - fin >> j; - Tokenizer tokenizer; - j.get_to(tokenizer); - return tokenizer; -} - -Tokenizer Tokenizer::LoadFromStr(const std::string& json_str) { - auto jo = nlohmann::json::parse(json_str); - Tokenizer tokenizer; - jo.get_to(tokenizer); - return tokenizer; -} - -void Tokenizer::Decode(const std::vector& token_ids, - std::string* result, - bool skip_special_tokens) const { - // Get tokens - std::vector tokens; - std::string token; - for (int i = 0; i < token_ids.size(); ++i) { - IdToToken(token_ids[i], &token); - if (!added_vocabulary_.IsSpecialToken(token) || !skip_special_tokens) { - tokens.push_back(token); - } - } - if (decoder_ != nullptr) { - (*decoder_)(tokens, result); - } else { - for (int i = 0; i < tokens.size(); ++i) { - if (i > 0) { - *result += " "; - } - *result += tokens[i]; - } - } -} - - -void Tokenizer::MultiThreadDecodeBatch( - const std::vector>& batch_token_ids, - std::vector* results, - bool skip_special_tokens, - size_t start_index, - size_t step_index) const { - auto batch_size = batch_token_ids.size(); - size_t end_index = start_index + step_index; - if (end_index > batch_size) end_index = batch_size; - for (size_t i = start_index; i < end_index; ++i) { - Decode(batch_token_ids[i], &(*results)[i], skip_special_tokens); - } -} - -void Tokenizer::DecodeBatch( - const std::vector>& batch_token_ids, - std::vector* results, - bool skip_special_tokens) const { - auto batch_size = batch_token_ids.size(); - results->resize(batch_size); - auto func = [&](size_t start_index, size_t step_index) { - MultiThreadDecodeBatch( - batch_token_ids, results, skip_special_tokens, start_index, step_index); - }; - RunMultiThread(func, batch_size); -} - -bool Tokenizer::GetUseTruncation() const { return use_truncation_; } - -bool Tokenizer::GetUsePadding() const { return use_padding_; } - -void to_json(nlohmann::json& j, const Tokenizer& tokenizer) { - j = { - {"added_tokens", tokenizer.added_vocabulary_}, - }; - - j["truncation"] = nullptr; - if (tokenizer.use_truncation_) { - j["truncation"] = tokenizer.trunc_method_; - } - - j["padding"] = nullptr; - if (tokenizer.use_padding_) { - j["padding"] = tokenizer.pad_method_; - } - - j["normalizer"] = nullptr; - if (tokenizer.normalizer_ != nullptr) { - if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::BertNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::ReplaceNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::StripNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::StripAccentsNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::NFCNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::NFDNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::NFKCNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::NFKDNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::NmtNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::LowercaseNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::SequenceNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } else if (typeid(*tokenizer.normalizer_.get()) == - typeid(normalizers::PrecompiledNormalizer)) { - j["normalizer"] = *dynamic_cast( - tokenizer.normalizer_.get()); - } - } - - j["pretokenizer"] = nullptr; - if (tokenizer.pretokenizer_ != nullptr) { - if (typeid(*tokenizer.pretokenizer_.get()) == - typeid(pretokenizers::BertPreTokenizer)) { - j["pretokenizer"] = *dynamic_cast( - tokenizer.pretokenizer_.get()); - } else if (typeid(*tokenizer.pretokenizer_.get()) == - typeid(pretokenizers::MetaSpacePreTokenizer)) { - j["pretokenizer"] = *dynamic_cast( - tokenizer.pretokenizer_.get()); - } else if (typeid(*tokenizer.pretokenizer_.get()) == - typeid(pretokenizers::WhitespacePreTokenizer)) { - j["pretokenizer"] = *dynamic_cast( - tokenizer.pretokenizer_.get()); - } else if (typeid(*tokenizer.pretokenizer_.get()) == - typeid(pretokenizers::WhitespaceAndPunctuationPreTokenizer)) { - j["pretokenizer"] = - *dynamic_cast( - tokenizer.pretokenizer_.get()); - } else if (typeid(*tokenizer.pretokenizer_.get()) == - typeid(pretokenizers::SequencePreTokenizer)) { - j["pretokenizer"] = *dynamic_cast( - tokenizer.pretokenizer_.get()); - } else if (typeid(*tokenizer.pretokenizer_.get()) == - typeid(pretokenizers::ByteLevelPreTokenizer)) { - j["pretokenizer"] = *dynamic_cast( - tokenizer.pretokenizer_.get()); - } else if (typeid(*tokenizer.pretokenizer_.get()) == - typeid(pretokenizers::SplitPreTokenizer)) { - j["pretokenizer"] = *dynamic_cast( - tokenizer.pretokenizer_.get()); - } - } - - j["model"] = nullptr; - if (tokenizer.model_ != nullptr) { - if (typeid(*tokenizer.model_.get()) == typeid(models::WordPiece)) { - j["model"] = *dynamic_cast(tokenizer.model_.get()); - } else if (typeid(*tokenizer.model_.get()) == - typeid(models::FastWordPiece)) { - j["model"] = - *dynamic_cast(tokenizer.model_.get()); - } else if (typeid(*tokenizer.model_.get()) == typeid(models::BPE)) { - j["model"] = *dynamic_cast(tokenizer.model_.get()); - } else if (typeid(*tokenizer.model_.get()) == typeid(models::Unigram)) { - j["model"] = *dynamic_cast(tokenizer.model_.get()); - } - } - - j["postprocessor"] = nullptr; - if (tokenizer.post_processor_ != nullptr) { - if (typeid(*tokenizer.post_processor_.get()) == - typeid(postprocessors::BertPostProcessor)) { - j["postprocessor"] = *dynamic_cast( - tokenizer.post_processor_.get()); - } else if (typeid(*tokenizer.post_processor_.get()) == - typeid(postprocessors::TemplatePostProcessor)) { - j["postprocessor"] = - *dynamic_cast( - tokenizer.post_processor_.get()); - } else if (typeid(*tokenizer.post_processor_.get()) == - typeid(postprocessors::RobertaPostProcessor)) { - j["postprocessor"] = *dynamic_cast( - tokenizer.post_processor_.get()); - } else if (typeid(*tokenizer.post_processor_.get()) == - typeid(postprocessors::ByteLevelPostProcessor)) { - j["postprocessor"] = - *dynamic_cast( - tokenizer.post_processor_.get()); - } - } - - j["decoder"] = nullptr; - if (tokenizer.decoder_ != nullptr) { - if (typeid(*tokenizer.decoder_.get()) == typeid(decoders::WordPiece)) { - j["decoder"] = - *dynamic_cast(tokenizer.decoder_.get()); - } - } -} - -void from_json(const nlohmann::json& j, Tokenizer& tokenizer) { - // deserialize normalizer_ - try { - const auto& normalizer = j.at("normalizer"); - if (!normalizer.is_null()) { - if (normalizer.at("type") == "BertNormalizer") { - normalizers::BertNormalizer bert_normalizer; - normalizer.get_to(bert_normalizer); - tokenizer.SetNormalizer(bert_normalizer); - } else if (normalizer.at("type") == "ReplaceNormalizer") { - normalizers::ReplaceNormalizer replace_normalizer; - normalizer.get_to(replace_normalizer); - tokenizer.SetNormalizer(replace_normalizer); - } else if (normalizer.at("type") == "StripNormalizer") { - normalizers::StripNormalizer strip_normalizer; - normalizer.get_to(strip_normalizer); - tokenizer.SetNormalizer(strip_normalizer); - } else if (normalizer.at("type") == "StripAccentsNormalizer") { - normalizers::StripAccentsNormalizer strip_normalizer; - normalizer.get_to(strip_normalizer); - tokenizer.SetNormalizer(strip_normalizer); - } else if (normalizer.at("type") == "NFCNormalizer") { - normalizers::NFCNormalizer unicode_normalizer; - normalizer.get_to(unicode_normalizer); - tokenizer.SetNormalizer(unicode_normalizer); - } else if (normalizer.at("type") == "NFDNormalizer") { - normalizers::NFDNormalizer unicode_normalizer; - normalizer.get_to(unicode_normalizer); - tokenizer.SetNormalizer(unicode_normalizer); - } else if (normalizer.at("type") == "NFKCNormalizer") { - normalizers::NFKCNormalizer unicode_normalizer; - normalizer.get_to(unicode_normalizer); - tokenizer.SetNormalizer(unicode_normalizer); - } else if (normalizer.at("type") == "NFKDNormalizer") { - normalizers::NFKDNormalizer unicode_normalizer; - normalizer.get_to(unicode_normalizer); - tokenizer.SetNormalizer(unicode_normalizer); - } else if (normalizer.at("type") == "NmtNormalizer") { - normalizers::NmtNormalizer unicode_normalizer; - normalizer.get_to(unicode_normalizer); - tokenizer.SetNormalizer(unicode_normalizer); - } else if (normalizer.at("type") == "LowercaseNormalizer") { - normalizers::LowercaseNormalizer unicode_normalizer; - normalizer.get_to(unicode_normalizer); - tokenizer.SetNormalizer(unicode_normalizer); - } else if (normalizer.at("type") == "SequenceNormalizer") { - normalizers::SequenceNormalizer unicode_normalizer; - normalizer.get_to(unicode_normalizer); - tokenizer.SetNormalizer(unicode_normalizer); - } else if (normalizer.at("type") == "PrecompiledNormalizer") { - normalizers::PrecompiledNormalizer precompiled_normalizer; - normalizer.get_to(precompiled_normalizer); - tokenizer.SetNormalizer(precompiled_normalizer); - } - } - - // deserialize pretokenizer_ - nlohmann::json pretokenizer; - if (j.find("pretokenizer") == j.end()) { - pretokenizer = j.at("pre_tokenizer"); - } else { - pretokenizer = j.at("pretokenizer"); - } - if (!pretokenizer.is_null()) { - if (pretokenizer.at("type") == "BertPreTokenizer") { - pretokenizers::BertPreTokenizer bert_pretokenizer; - tokenizer.SetPreTokenizer(bert_pretokenizer); - } else if (pretokenizer.at("type") == "MetaSpacePreTokenizer") { - pretokenizers::MetaSpacePreTokenizer meta_pretokenizer; - pretokenizer.get_to(meta_pretokenizer); - tokenizer.SetPreTokenizer(meta_pretokenizer); - } else if (pretokenizer.at("type") == "WhitespacePreTokenizer") { - pretokenizers::WhitespacePreTokenizer whitespace_pretokenizer; - tokenizer.SetPreTokenizer(whitespace_pretokenizer); - } else if (pretokenizer.at("type") == - "WhitespaceAndPunctuationPreTokenizer") { - pretokenizers::WhitespaceAndPunctuationPreTokenizer - whitespace_pretokenizer; - tokenizer.SetPreTokenizer(whitespace_pretokenizer); - } else if (pretokenizer.at("type") == "SequencePreTokenizer") { - pretokenizers::SequencePreTokenizer sequence_pretokenizer; - pretokenizer.get_to(sequence_pretokenizer); - tokenizer.SetPreTokenizer(sequence_pretokenizer); - } else if (pretokenizer.at("type") == "ByteLevelPreTokenizer") { - pretokenizers::ByteLevelPreTokenizer byte_pretokenizer; - pretokenizer.get_to(byte_pretokenizer); - tokenizer.SetPreTokenizer(byte_pretokenizer); - } else if (pretokenizer.at("type") == "SplitPreTokenizer") { - pretokenizers::SplitPreTokenizer split_pretokenizer; - pretokenizer.get_to(split_pretokenizer); - tokenizer.SetPreTokenizer(split_pretokenizer); - } - } - - // deserialize model_ - const auto& model = j.at("model"); - if (!model.is_null()) { - if (model.at("type") == "WordPiece") { - models::WordPiece wordpiece; - model.get_to(wordpiece); - tokenizer.SetModel(wordpiece); - } else if (model.at("type") == "FastWordPiece") { - models::FastWordPiece wordpiece; - model.get_to(wordpiece); - tokenizer.SetModel(wordpiece); - } else if (model.at("type") == "BPE") { - models::BPE bpe; - model.get_to(bpe); - tokenizer.SetModel(bpe); - } else if (model.at("type") == "Unigram") { - models::Unigram unigram; - model.get_to(unigram); - tokenizer.SetModel(unigram); - } - } - - // deserialize post_processor_ - nlohmann::json post_processor; - if (j.find("postprocessor") == j.end()) { - post_processor = j.at("post_processor"); - } else { - post_processor = j.at("postprocessor"); - } - if (!post_processor.is_null()) { - if (post_processor.at("type") == "BertPostProcessor") { - postprocessors::BertPostProcessor bert_postprocessor; - post_processor.get_to(bert_postprocessor); - tokenizer.SetPostProcessor(bert_postprocessor); - } else if (post_processor.at("type") == "TemplateProcessing") { - postprocessors::TemplatePostProcessor template_postprocessor; - post_processor.get_to(template_postprocessor); - tokenizer.SetPostProcessor(template_postprocessor); - } else if (post_processor.at("type") == "RobertaPostProcessor") { - postprocessors::RobertaPostProcessor roberta_postprocessor; - post_processor.get_to(roberta_postprocessor); - tokenizer.SetPostProcessor(roberta_postprocessor); - } else if (post_processor.at("type") == "ByteLevelPostProcessor") { - postprocessors::ByteLevelPostProcessor byte_level_postprocessor; - post_processor.get_to(byte_level_postprocessor); - tokenizer.SetPostProcessor(byte_level_postprocessor); - } - } - - // deserialize trunc_method_ - const auto& trunc_method = j.at("truncation"); - if (!trunc_method.is_null()) { - tokenizer.use_truncation_ = true; - trunc_method.get_to(tokenizer.trunc_method_); - } else { - tokenizer.use_truncation_ = false; - } - - // deserialize pad_method_ - const auto& pad_method = j.at("padding"); - if (!pad_method.is_null()) { - tokenizer.use_padding_ = true; - pad_method.get_to(tokenizer.pad_method_); - } else { - tokenizer.use_padding_ = false; - } - - // deserialize added_vocabulary_ - const auto& added_tokens = j.at("added_tokens"); - core::AddedTokenWithId added_token_with_id; - std::vector tokens(added_tokens.size()); - for (int i = 0; i < added_tokens.size(); ++i) { - added_tokens[i].get_to(added_token_with_id); - tokens[i] = added_token_with_id.added_token_; - } - tokenizer.AddSpecialTokens(tokens); - - const auto& decoder = j.at("decoder"); - if (!decoder.is_null()) { - if (decoder.at("type") == "WordPiece") { - decoders::WordPiece wordpiece_decoder; - decoder.get_to(wordpiece_decoder); - tokenizer.SetDecoder(wordpiece_decoder); - } - } - - } catch (nlohmann::json::out_of_range& e) { - VLOG(0) << e.what(); - } -} -// Instantiate normalizers -template void Tokenizer::SetNormalizer(const normalizers::BertNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::LowercaseNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::NFCNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::NFKCNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::NFDNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::NFKDNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::NmtNormalizer&); -template void Tokenizer::SetNormalizer( - const normalizers::PrecompiledNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::ReplaceNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::SequenceNormalizer&); -template void Tokenizer::SetNormalizer( - const normalizers::StripAccentsNormalizer&); -template void Tokenizer::SetNormalizer(const normalizers::StripNormalizer&); - -// Instantiate pretokenizers -template void Tokenizer::SetPreTokenizer( - const pretokenizers::BertPreTokenizer&); -template void Tokenizer::SetPreTokenizer( - const pretokenizers::WhitespacePreTokenizer&); -template void Tokenizer::SetPreTokenizer( - const pretokenizers::WhitespaceAndPunctuationPreTokenizer&); -template void Tokenizer::SetPreTokenizer( - const pretokenizers::MetaSpacePreTokenizer&); -template void Tokenizer::SetPreTokenizer( - const pretokenizers::SequencePreTokenizer&); -template void Tokenizer::SetPreTokenizer( - const pretokenizers::ByteLevelPreTokenizer&); -template void Tokenizer::SetPreTokenizer( - const pretokenizers::SplitPreTokenizer&); - -// Instantiate models -template Tokenizer::Tokenizer(const models::WordPiece&); -template void Tokenizer::SetModel(const models::WordPiece&); -template Tokenizer::Tokenizer(const models::FastWordPiece&); -template void Tokenizer::SetModel(const models::FastWordPiece&); -template Tokenizer::Tokenizer(const models::BPE&); -template void Tokenizer::SetModel(const models::BPE&); -template Tokenizer::Tokenizer(const models::Unigram&); -template void Tokenizer::SetModel(const models::Unigram&); - -// Instantiate processors -template void Tokenizer::SetPostProcessor( - const postprocessors::BertPostProcessor&); -template void Tokenizer::SetPostProcessor( - const postprocessors::TemplatePostProcessor&); -template void Tokenizer::SetPostProcessor( - const postprocessors::RobertaPostProcessor&); -template void Tokenizer::SetPostProcessor( - const postprocessors::ByteLevelPostProcessor&); - -// Instantiate Decoder -template void Tokenizer::SetDecoder(const decoders::WordPiece& decoder); -} // namespace core -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/core/tokenizer.h b/fast_tokenizer/fast_tokenizer/core/tokenizer.h deleted file mode 100644 index 71fdf6099f64..000000000000 --- a/fast_tokenizer/fast_tokenizer/core/tokenizer.h +++ /dev/null @@ -1,254 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include // For shared_ptr -#include - -#include "fast_tokenizer/core/added_vocabulary.h" -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/utils/utils.h" -#include "fast_tokenizer/utils/variant.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { - -namespace normalizers { - -class Normalizer; -class NormalizedString; - -} // namespace normalizers - -namespace pretokenizers { - -class PreTokenizer; -class PreTokenizedString; - -} // namespace pretokenizers - -namespace models { -class Model; -} // namespace models - -namespace postprocessors { -class PostProcessor; -} // namespace postprocessors - -namespace decoders { -class Decoder; -}; - -namespace core { - -class AddedVocabulary; -class Encoding; - -using InputString = paddlenlp::variant>; -using EncodeInput = - paddlenlp::variant>; - -class FASTTOKENIZER_DECL Tokenizer { -public: - Tokenizer() - : model_(nullptr), - normalizer_(nullptr), - pretokenizer_(nullptr), - post_processor_(nullptr), - decoder_(nullptr), - use_padding_(true), - use_truncation_(true) {} - template - Tokenizer(const ModelType& model) - : model_(std::make_shared(model)), - normalizer_(nullptr), - pretokenizer_(nullptr), - post_processor_(nullptr), - decoder_(nullptr), - use_padding_(true), - use_truncation_(true) {} - - template - void SetNormalizer(const NormalizerType& normalizer) { - normalizer_ = std::make_shared(normalizer); - } - void ReleaseNormaizer(); - normalizers::Normalizer* GetNormalizerPtr() const; - - template - void SetPreTokenizer(const PreTokenizerType& pretokenizer) { - pretokenizer_ = std::make_shared(pretokenizer); - } - void ReleasePreTokenizer(); - pretokenizers::PreTokenizer* GetPreTokenizer() const; - - template - void SetModel(const ModelType& model) { - model_ = std::make_shared(model); - } - models::Model* GetModelPtr() const; - - template - void SetPostProcessor(const PostProcessorType& post_processor) { - post_processor_ = std::make_shared(post_processor); - } - void ReleasePostProcessor(); - postprocessors::PostProcessor* GetPostProcessorPtr() const; - - template - void SetDecoder(const DecoderType& decoder) { - decoder_ = std::make_shared(decoder); - } - void ReleaseDecoder(); - decoders::Decoder* GetDecoderPtr() const; - - void SetTruncMethod(const TruncMethod& trunc_method); - void DisableTruncMethod(); - void EnableTruncMethod(size_t max_len, - size_t stride, - Direction direction, - TruncStrategy strategy); - TruncMethod GetTruncMethod() const; - - void SetPadMethod(const PadMethod& pad_method); - void DisablePadMethod(); - void EnablePadMethod(Direction direction, - uint32_t pad_id, - uint32_t pad_type_id, - const std::string& pad_token, - uint32_t* length, - uint32_t* pad_to_multiple_of); - PadMethod GetPadMethod() const; - - Vocab GetVocab(bool with_added_vocabulary = true) const; - size_t GetVocabSize(bool with_added_vocabulary = true) const; - bool TokenToId(const std::string& token, uint32_t* id) const; - bool IdToToken(uint32_t id, std::string* token) const; - size_t AddTokens(const std::vector& tokens); - size_t AddSpecialTokens(const std::vector& tokens); - bool DoTokenize(pretokenizers::PreTokenizedString* pretokenized, - uint32_t type_id, - const std::vector& word_idx, - OffsetType offset_type, - Encoding* encoding) const; - bool DoPreTokenize(pretokenizers::PreTokenizedString* pretokenized) const; - - void EncodeSingleString(const InputString& input_string, - uint32_t type_id, - OffsetType offset_type, - Encoding* encodings) const; - void PostProcess(Encoding* encoding, - Encoding* pair_encoding, - bool add_special_tokens, - Encoding* result_encoding) const; - void EncodePairStrings(const EncodeInput& encode_input, - Encoding* encodings, - bool add_special_tokens = true) const; - void EncodePairStrings(const std::string& text, - const std::string& text_pair, - Encoding* encodings, - bool add_special_tokens = true) const; - - void MultiThreadEncodeBatchStrings( - const std::vector& batch_encode_input, - std::vector* encodings, - bool add_special_tokens, - size_t start_index, - size_t step_index) const; - // Tokenize the unpretokenized text. - void MultiThreadEncodeBatchStrings(const std::vector& texts, - std::vector* encodings, - bool add_special_tokens, - size_t start_index, - size_t step_index) const; - void MultiThreadEncodeBatchStrings(const std::vector& texts, - const std::vector& text_pairs, - std::vector* encodings, - bool add_special_tokens, - size_t start_index, - size_t step_index) const; - - void EncodeBatchStrings(const std::vector& batch_encode_input, - std::vector* encodings, - bool add_special_tokens = true) const; - // Tokenize the unpretokenized text. - void EncodeBatchStrings(const std::vector& texts, - std::vector* encodings, - bool add_special_tokens = true) const; - void EncodeBatchStrings(const std::vector& texts, - const std::vector& text_pairs, - std::vector* encodings, - bool add_special_tokens = true) const; - - // Encode single text which is already pretokenized. - void EncodeSingleText(const std::vector& pretokenized_texts, - uint32_t type_id, - OffsetType offset_type, - Encoding* encodings) const; - // Encode single raw text - void EncodeSingleText(const std::string& raw_text, - uint32_t type_id, - OffsetType offset_type, - Encoding* encodings) const; - const AddedVocabulary& GetAddedVocabulary() const; - void Save(const std::string& json_path, bool pretty = true) const; - void ToJsonStr(std::string* json_str, bool pretty = true) const; - - // Create a tokenzier from json path - static Tokenizer LoadFromFile(const std::string& json_path); - static Tokenizer LoadFromStr(const std::string& json_str); - - bool GetUseTruncation() const; - bool GetUsePadding() const; - - // Decode: From tokens to a complete string - void Decode(const std::vector& token_ids, - std::string* result, - bool skip_special_tokens = true) const; - void MultiThreadDecodeBatch( - const std::vector>& batch_token_ids, - std::vector* results, - bool skip_special_tokens, - size_t start_index, - size_t step_index) const; - void DecodeBatch(const std::vector>& batch_token_ids, - std::vector* results, - bool skip_special_tokens = true) const; - -private: - Encoding EncodeTextToEncoding(const std::vector& word_idx, - uint32_t type_id, - OffsetType offset_type, - const std::string& text) const; - // All member of Tokenizer - std::shared_ptr normalizer_; - std::shared_ptr pretokenizer_; - std::shared_ptr model_; - std::shared_ptr post_processor_; - std::shared_ptr decoder_; - - TruncMethod trunc_method_; - PadMethod pad_method_; - AddedVocabulary added_vocabulary_; - bool use_truncation_; - bool use_padding_; - - friend void to_json(nlohmann::json& j, const Tokenizer& tokenizer); - friend void from_json(const nlohmann::json& j, Tokenizer& tokenizer); -}; - -} // namespace core -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/decoders/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/decoders/CMakeLists.txt deleted file mode 100644 index d2fffc3dac6e..000000000000 --- a/fast_tokenizer/fast_tokenizer/decoders/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -cc_library(decoders SRCS wordpiece.cc DEPS json utils) diff --git a/fast_tokenizer/fast_tokenizer/decoders/decoder.h b/fast_tokenizer/fast_tokenizer/decoders/decoder.h deleted file mode 100644 index 7969e22d11f7..000000000000 --- a/fast_tokenizer/fast_tokenizer/decoders/decoder.h +++ /dev/null @@ -1,32 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace decoders { - -struct FASTTOKENIZER_DECL Decoder { - virtual void operator()(const std::vector tokens, - std::string* result) const = 0; -}; - -} // namespace decoders -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/decoders/decoders.h b/fast_tokenizer/fast_tokenizer/decoders/decoders.h deleted file mode 100644 index efc72779de9f..000000000000 --- a/fast_tokenizer/fast_tokenizer/decoders/decoders.h +++ /dev/null @@ -1,18 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/decoders/decoder.h" -#include "fast_tokenizer/decoders/wordpiece.h" \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/decoders/wordpiece.cc b/fast_tokenizer/fast_tokenizer/decoders/wordpiece.cc deleted file mode 100644 index e81f1562d242..000000000000 --- a/fast_tokenizer/fast_tokenizer/decoders/wordpiece.cc +++ /dev/null @@ -1,69 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/decoders/wordpiece.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace decoders { - -WordPiece::WordPiece(const std::string prefix, bool cleanup) - : prefix_(prefix), cleanup_(cleanup) {} - -void WordPiece::CleanUp(std::string* result) const { - utils::StringReplaceAll(result, " .", "."); - utils::StringReplaceAll(result, " !", "!"); - utils::StringReplaceAll(result, " ?", "?"); - utils::StringReplaceAll(result, " ,", ","); - utils::StringReplaceAll(result, " ' ", "'"); - utils::StringReplaceAll(result, " n't", "n't"); - utils::StringReplaceAll(result, " 'm", "'m"); - utils::StringReplaceAll(result, " do not", " don't"); - utils::StringReplaceAll(result, " 's", "'s"); - utils::StringReplaceAll(result, " 've", "'ve"); - utils::StringReplaceAll(result, " 're", "'re"); -} - -void WordPiece::operator()(const std::vector tokens, - std::string* result) const { - *result = ""; - for (int i = 0; i < tokens.size(); ++i) { - if (i > 0) { - *result += " "; - } - *result += tokens[i]; - } - utils::StringReplaceAll(result, " " + prefix_, ""); - if (cleanup_) { - CleanUp(result); - } -} - -void to_json(nlohmann::json& j, const WordPiece& decoder) { - j = { - {"type", "WordPiece"}, - {"cleanup", decoder.cleanup_}, - {"prefix", decoder.prefix_}, - }; -} - -void from_json(const nlohmann::json& j, WordPiece& decoder) { - j["cleanup"].get_to(decoder.cleanup_); - j["prefix"].get_to(decoder.prefix_); -} - -} // namespace decoders -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/decoders/wordpiece.h b/fast_tokenizer/fast_tokenizer/decoders/wordpiece.h deleted file mode 100644 index 1f41b3f8b5dc..000000000000 --- a/fast_tokenizer/fast_tokenizer/decoders/wordpiece.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/decoders/decoder.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace decoders { - -struct FASTTOKENIZER_DECL WordPiece : public Decoder { - virtual void operator()(const std::vector tokens, - std::string* result) const; - - WordPiece(const std::string prefix = "##", bool cleanup = true); - -private: - void CleanUp(std::string* result) const; - std::string prefix_; - bool cleanup_; - - friend void to_json(nlohmann::json& j, const WordPiece& decoder); - friend void from_json(const nlohmann::json& j, WordPiece& decoder); -}; - -} // namespace decoders -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/models/CMakeLists.txt deleted file mode 100644 index f51706a77035..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -cc_library(models - SRCS wordpiece.cc fast_wordpiece.cc bpe.cc unigram.cc - DEPS core json trie failure icuuc icudata lattice utils) diff --git a/fast_tokenizer/fast_tokenizer/models/bpe.cc b/fast_tokenizer/fast_tokenizer/models/bpe.cc deleted file mode 100644 index ad80d2752d6f..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/bpe.cc +++ /dev/null @@ -1,349 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include -#include -#include - -#include "glog/logging.h" -#include "fast_tokenizer/models/bpe.h" -#include "fast_tokenizer/utils/path.h" -#include "fast_tokenizer/utils/utf8.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { -const std::string WHITESPACE = " \n\r\t\f\v"; - -void BPE::Init(const core::Merges& merges) { - if (dropout_.size() > 0) { - if (dropout_[0] > 1.0 || dropout_[0] <= 0.0) { - std::ostringstream oss; - oss << "The range of dropout rate should be (0,1], but receive " - << dropout_[0]; - throw std::runtime_error(oss.str()); - } - } - // construct vocab_r - for (auto&& item : vocab_) { - vocab_reversed_[item.second] = item.first; - } - int prefix_len = 0; - if (continuing_subword_prefix_.size() > 0) { - prefix_len += continuing_subword_prefix_[0].length(); - } - - // construct merge_map - for (int i = 0; i < merges.size(); i++) { - auto&& merge = merges[i]; - try { - auto a_id = vocab_.at(merge.first); - auto b_id = vocab_.at(merge.second); - auto new_token = merge.first + merge.second.substr(prefix_len); - auto new_id = vocab_.at(new_token); - merges_.insert({core::Pair(a_id, b_id), {i, new_id}}); - } catch (...) { - std::ostringstream oss; - oss << "Can't merge token out of the vocabulary"; - throw std::runtime_error(oss.str()); - } - } - - // construct unk - if (unk_token_.size() > 0) { - try { - unk_token_id_.emplace_back(vocab_.at(unk_token_.front())); - } catch (...) { - std::ostringstream oss; - oss << "Unk token `" << unk_token_.front() - << "` not found in the vocabulary"; - throw std::runtime_error(oss.str()); - } - } -} - -BPE::BPE() : fuse_unk_(false), cache_(utils::DEFAULT_CACHE_CAPACITY) {} - -BPE::BPE(const core::Vocab& vocab, - const core::Merges& merges, - size_t cache_capacity, - const std::vector& dropout, - const std::vector& unk_token, - const std::vector& continuing_subword_prefix, - const std::vector& end_of_word_suffix, - bool fuse_unk) - : vocab_(vocab), - fuse_unk_(fuse_unk), - dropout_(dropout), - unk_token_(unk_token), - continuing_subword_prefix_(continuing_subword_prefix), - end_of_word_suffix_(end_of_word_suffix), - cache_(utils::DEFAULT_CACHE_CAPACITY) { - Init(merges); -} - -void BPE::ClearCache() { cache_.Clear(); } - -core::Vocab BPE::GetVocabFromFile(const std::string& vocab_json_path) { - std::ifstream fin(vocab_json_path); - core::Vocab vocab; - nlohmann::json j; - fin >> j; - for (nlohmann::json::iterator it = j.begin(); it != j.end(); ++it) { - vocab[it.key()] = it.value(); - } - return vocab; -} - -void BPE::ConstructMergesPair(const std::string word_line, - std::pair* result) { - auto pair_a_begin = word_line.find_first_not_of(WHITESPACE); - auto pair_a_end = word_line.find_first_of(WHITESPACE, pair_a_begin); - auto pair_b_begin = word_line.find_first_not_of(WHITESPACE, pair_a_end); - auto pair_b_end = word_line.find_first_of(WHITESPACE, pair_b_begin); - *result = {word_line.substr(pair_a_begin, pair_a_end - pair_a_begin), - word_line.substr(pair_b_begin, pair_b_end - pair_b_begin)}; -} - -core::Merges BPE::GetMergesFromFile(const std::string& merge_path) { - std::ifstream fin(merge_path); - core::Merges merges; - std::string word_str; - while (std::getline(fin, word_str)) { - if (word_str.find("#version") == 0) { - continue; - } - std::pair result; - ConstructMergesPair(word_str, &result); - merges.emplace_back(result); - } - return merges; -} - -void BPE::GetVocabAndMergesFromFile(const std::string& vocab_json_path, - const std::string& merge_path, - core::Vocab* vocab, - core::Merges* merges) { - *vocab = BPE::GetVocabFromFile(vocab_json_path); - *merges = BPE::GetMergesFromFile(merge_path); -} - -void BPE::MergeWord(const std::string& word, core::BPEWord* bpe_word) { - std::vector> unk; - bpe_word->Reserve(word.length()); - uint32_t start = 0; - while (start < word.length()) { - uint32_t content_char; - uint32_t content_char_width = - utils::UTF8ToUInt32(word.data() + start, &content_char); - content_char = utils::UTF8ToUnicode(content_char); - uint32_t end = start + content_char_width; - bool is_first = (start == 0); - bool is_last = (end >= word.length()); - std::string curr_str = word.substr(start, content_char_width); - // Add the `continuing_subword_prefix` if relevant - if (!is_first) { - if (continuing_subword_prefix_.size() > 0) { - curr_str = continuing_subword_prefix_.front() + curr_str; - } - } - // Add the `end_of_word_suffix` if relevant - if (is_last) { - if (end_of_word_suffix_.size() > 0) { - curr_str = curr_str + end_of_word_suffix_.front(); - } - } - if (vocab_.find(curr_str) != vocab_.end()) { - if (unk.size() > 0) { - bpe_word->Add(unk.front().first, unk.front().second); - unk.clear(); - } - auto id = vocab_.at(curr_str); - bpe_word->Add(id, content_char_width); - } else { - if (unk_token_id_.size() > 0) { - if (unk.size() == 0) { - unk.push_back({unk_token_id_.front(), content_char_width}); - } else { - if (fuse_unk_) { - unk[0] = {unk[0].first, unk[0].second + content_char_width}; - } else { - bpe_word->Add(unk[0].first, unk[0].second); - unk[0] = {unk_token_id_.front(), content_char_width}; - } - } - } - } - start = end; - } - - if (unk.size() > 0) { - bpe_word->Add(unk.front().first, unk.front().second); - } - bpe_word->MergeAll(merges_, dropout_); -} - -void BPE::WordToTokens(const core::BPEWord& bpe_word, - std::vector* tokens) { - std::vector chars; - bpe_word.GetChars(&chars); - - std::vector offsets; - bpe_word.GetOffset(&offsets); - - tokens->reserve(offsets.size()); - for (int i = 0; i < offsets.size(); ++i) { - tokens->emplace_back(chars[i], vocab_reversed_[chars[i]], offsets[i]); - } -} - -void BPE::TokenizeWithCache(const std::string& sequence, - std::vector* tokens) { - core::BPEWord bpe_word; - if (cache_.GetValue(sequence, &bpe_word)) { - WordToTokens(bpe_word, tokens); - } else { - MergeWord(sequence, &bpe_word); - WordToTokens(bpe_word, tokens); - cache_.SetValue(sequence, bpe_word); - } -} - -std::vector BPE::Tokenize(const std::string& sequence) { - std::vector tokens; - if (sequence.empty()) { - return tokens; - } - if (dropout_.size() == 0) { - TokenizeWithCache(sequence, &tokens); - return tokens; - } - core::BPEWord bpe_word; - MergeWord(sequence, &bpe_word); - WordToTokens(bpe_word, &tokens); - return tokens; -} - -bool BPE::TokenToId(const std::string& token, uint32_t* id) const { - if (vocab_.find(token) == vocab_.end()) { - return false; - } - *id = vocab_.at(token); - return true; -} - -bool BPE::IdToToken(uint32_t id, std::string* token) const { - if (vocab_reversed_.find(id) == vocab_reversed_.end()) { - return false; - } - *token = vocab_reversed_.at(id); - return true; -} - -core::Vocab BPE::GetVocab() const { return vocab_; } - -size_t BPE::GetVocabSize() const { return vocab_.size(); } - -// Return the saved voacb path and merges.txt -std::vector BPE::Save(const std::string& folder, - const std::string& filename_prefix) const { - // write vocab json - std::string vocab_path; - if (filename_prefix == "") { - vocab_path = utils::PathJoin(folder, "vocab.json"); - } else { - vocab_path = utils::PathJoin({folder, filename_prefix, "-vocab.json"}); - } - VLOG(6) << "Vocab path" << vocab_path; - core::SortedVocabReversed sorted_vocab_r(vocab_reversed_.begin(), - vocab_reversed_.end()); - nlohmann::json j = sorted_vocab_r; - std::ofstream fout(vocab_path); - fout << j.dump(); - fout.close(); - - // write merges.txt - std::string merges_path; - if (filename_prefix == "") { - merges_path = utils::PathJoin(folder, "merges.txt"); - } else { - merges_path = utils::PathJoin({folder, filename_prefix, "-merges.txt"}); - } - VLOG(6) << "Merges path" << merges_path; - std::ofstream merge_fout(merges_path); - merge_fout << "#version: 0.2\n"; - for (auto&& merge : merges_) { - merge_fout << vocab_reversed_.at(merge.first.first) << " " - << vocab_reversed_.at(merge.first.second) << "\n"; - } - merge_fout.close(); - return {vocab_path, merges_path}; -} - -void to_json(nlohmann::json& j, const BPE& model) { - std::vector> merges; - for (auto& merge : model.merges_) { - merges.push_back({merge.first, merge.second.first}); - } - std::sort(merges.begin(), - merges.end(), - [](const std::pair& a, - const std::pair& b) { - return a.second < b.second; - }); - std::vector merge_strs; - for (auto& merge : merges) { - std::string s = model.vocab_reversed_.at(merge.first.first) + " " + - model.vocab_reversed_.at(merge.first.second); - merge_strs.push_back(s); - } - - core::SortedVocabReversed sorted_vocab_r(model.vocab_reversed_.begin(), - model.vocab_reversed_.end()); - - j = {{"type", "BPE"}, - {"unk_token", model.unk_token_}, - {"continuing_subword_prefix", model.continuing_subword_prefix_}, - {"end_of_word_suffix", model.end_of_word_suffix_}, - {"fuse_unk", model.fuse_unk_}, - {"dropout", model.dropout_}, - {"vocab", sorted_vocab_r}, - {"merges", merge_strs}}; -} - -void from_json(const nlohmann::json& j, BPE& model) { - j["vocab"].get_to(model.vocab_); - j["unk_token"].get_to(model.unk_token_); - j["continuing_subword_prefix"].get_to(model.continuing_subword_prefix_); - j["end_of_word_suffix"].get_to(model.end_of_word_suffix_); - j["fuse_unk"].get_to(model.fuse_unk_); - j["dropout"].get_to(model.dropout_); - - std::vector merge_strs; - j["merges"].get_to(merge_strs); - - core::Merges merges; - std::pair result; - for (auto& word_line : merge_strs) { - BPE::ConstructMergesPair(word_line, &result); - merges.push_back(result); - } - model.Init(merges); -} - -} // namespace model -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/bpe.h b/fast_tokenizer/fast_tokenizer/models/bpe.h deleted file mode 100644 index bb8cfd08cc41..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/bpe.h +++ /dev/null @@ -1,82 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/models/model.h" -#include "nlohmann/json.hpp" -#include "fast_tokenizer/utils/cache.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { - -struct FASTTOKENIZER_DECL BPE : public Model { - BPE(); - BPE(const core::Vocab& vocab, - const core::Merges& merges, - size_t cache_capacity = utils::DEFAULT_CACHE_CAPACITY, - const std::vector& dropout = {}, - const std::vector& unk_token = {}, - const std::vector& continuing_subword_prefix = {}, - const std::vector& end_of_word_suffix = {}, - bool fuse_unk = false); - virtual std::vector Tokenize( - const std::string& sequence) override; - virtual bool TokenToId(const std::string& token, uint32_t* id) const override; - virtual bool IdToToken(uint32_t id, std::string* token) const override; - virtual core::Vocab GetVocab() const override; - virtual size_t GetVocabSize() const override; - // Return the saved voacb path - virtual std::vector Save( - const std::string& folder, - const std::string& filename_prefix) const override; - - void ClearCache(); - static core::Vocab GetVocabFromFile(const std::string& vocab_json_path); - static core::Merges GetMergesFromFile(const std::string& merge_path); - static void GetVocabAndMergesFromFile(const std::string& vocab_json_path, - const std::string& merge_path, - core::Vocab* vocab, - core::Merges* merges); - static void ConstructMergesPair(const std::string word_line, - std::pair* result); - -private: - void Init(const core::Merges& merges); - void MergeWord(const std::string& word, core::BPEWord* bpe_word); - void WordToTokens(const core::BPEWord& bpe_word, - std::vector* tokens); - void TokenizeWithCache(const std::string& sequence, - std::vector* tokens); - core::Vocab vocab_; - core::VocabReversed vocab_reversed_; - core::MergeMap merges_; - - // The following vector may contain 0 or 1 element - utils::Cache cache_; - std::vector dropout_; - std::vector unk_token_; - std::vector unk_token_id_; - std::vector continuing_subword_prefix_; - std::vector end_of_word_suffix_; - bool fuse_unk_; - friend void to_json(nlohmann::json& j, const BPE& model); - friend void from_json(const nlohmann::json& j, BPE& model); -}; - -} // namespace models -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/fast_wordpiece.cc b/fast_tokenizer/fast_tokenizer/models/fast_wordpiece.cc deleted file mode 100644 index 92124c6b15ae..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/fast_wordpiece.cc +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2022 TF.Text Authors. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "fast_tokenizer/models/fast_wordpiece.h" - -#include -#include -#include -#include -#include - -#include "fast_tokenizer/models/wordpiece.h" -#include "fast_tokenizer/utils/path.h" -#include "fast_tokenizer/utils/utf8.h" -#include "fast_tokenizer/utils/utils.h" -#include "glog/logging.h" -#include "unicode/uchar.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { - -const std::string WHITESPACE = " \n\r\t\f\v"; - -void FastWordPiece::InitFailureAndTrie() { - unk_token_id_ = vocab_.at(unk_token_); - trie_.SetWithPretokenization(with_pretokenization_); - trie_.SetUNKToken(unk_token_); - trie_.SetContinuingSubwordPrefix(continuing_subword_prefix_); - failure_array_.SetWithPretokenization(with_pretokenization_); - failure_array_.InitFromVocabAndTrie( - vocab_, &trie_, unk_token_, continuing_subword_prefix_); - PrecomputeEncodeValueForSubwordPrefix(); -} - -FastWordPiece::FastWordPiece() : WordPiece(), with_pretokenization_(false) {} - -FastWordPiece::FastWordPiece(const core::Vocab& vocab, - const std::string& unk_token, - size_t max_input_chars_per_word, - const std::string& continuing_subword_prefix, - bool with_pretokenization) - : WordPiece(vocab, - unk_token, - max_input_chars_per_word, - continuing_subword_prefix), - trie_(continuing_subword_prefix, unk_token, with_pretokenization), - with_pretokenization_(with_pretokenization), - failure_array_(with_pretokenization) { - InitFailureAndTrie(); -} - -void FastWordPiece::PrecomputeEncodeValueForSubwordPrefix() { - auto subword_prefix_tokens = WordPiece::Tokenize(continuing_subword_prefix_); - encoded_value_for_subword_prefix_.reserve(subword_prefix_tokens.size()); - - for (auto& token : subword_prefix_tokens) { - utils::FailureVocabToken failure_vocab_token( - token.value_, token.id_, continuing_subword_prefix_); - int encoded_value = utils::EncodeToken( - failure_vocab_token.TokenId(), - failure_vocab_token.TokenLengthWithoutContinuingSubwordPrefix(), - failure_vocab_token.IsSuffixToken()); - encoded_value_for_subword_prefix_.push_back(encoded_value); - } -} - -bool FastWordPiece::TryFollowFailureLinkAndCollectTokens( - const std::string& sequence, - int sequence_offset_in_text, - int* curr_offset_in_sequence, - utils::Trie::TraversalCursor* node, - std::vector* tokens) const { - int curr_node_value = 0; - if (trie_.TryGetData(*node, &curr_node_value)) { - AppendTokensToOutput(sequence, - sequence_offset_in_text, - curr_offset_in_sequence, - curr_node_value, - tokens); - trie_.SetTraversalCursor( - node, failure_array_.GetFailure(node->node_id_)->failure_link_); - return true; - } - const auto& node_aux = failure_array_.GetFailure(node->node_id_); - - if (node_aux->failure_link_ == utils::kNullNode) { - // No failure_link can be followed. - return false; - } - int offset = 0, length = 0; - utils::GetFailurePopsOffsetAndLength( - node_aux->failure_pops_offset_length_, &offset, &length); - for (int i = offset; i < offset + length; ++i) { - AppendTokensToOutput(sequence, - sequence_offset_in_text, - curr_offset_in_sequence, - failure_array_.GetFailurePop(i), - tokens); - } - trie_.SetTraversalCursor(node, node_aux->failure_link_); - return true; -} - -void FastWordPiece::AppendTokensToOutput( - const std::string& sequence, - int sequence_offset_in_text, - int* curr_offset_in_sequence, - int curr_node_value, - std::vector* tokens) const { - uint32_t id = utils::GetTokenIdFromEncodedValue(curr_node_value); - std::string value; - int token_substr_length = - utils::GetTokenLengthFromEncodedValue(curr_node_value); - if (*curr_offset_in_sequence == 0 && - utils::IsSuffixTokenFromEncodedValue(curr_node_value)) { - token_substr_length += continuing_subword_prefix_.size(); - } - - if (id == unk_token_id_) { - value = unk_token_; - } else { - auto c_offset = *curr_offset_in_sequence; - c_offset = (std::min)(c_offset, static_cast(sequence.length() - 1)); - value = sequence.substr(*curr_offset_in_sequence, token_substr_length); - } - - if (*curr_offset_in_sequence > 0) { - value = continuing_subword_prefix_ + value; - } - core::Offset offset = { - sequence_offset_in_text + *curr_offset_in_sequence, - sequence_offset_in_text + *curr_offset_in_sequence + token_substr_length}; - tokens->emplace_back(id, value, offset); - - *curr_offset_in_sequence += token_substr_length; -} - -void FastWordPiece::ResetOutputAppendUNK( - int sequence_offset_in_text, - int sequence_size, - int* original_num_tokens, - std::vector* tokens) const { - tokens->resize(*original_num_tokens + 1); - tokens->back() = { - unk_token_id_, - unk_token_, - {sequence_offset_in_text, sequence_offset_in_text + sequence_size}}; - (*original_num_tokens)++; -} - -bool FastWordPiece::TryHandleContinuingSubWordPrefix( - const std::string& sequence, - int sequence_offset_in_text, - const utils::Trie::TraversalCursor& curr_node, - int* original_num_tokens, - int* curr_offset_in_sequence, - std::vector* tokens) const { - if (curr_node.node_id_ != trie_.GetSuffixRoot()) { - return false; - } - int cur_num_tokens = tokens->size(); - if (cur_num_tokens != *original_num_tokens) { - return false; - } - if (encoded_value_for_subword_prefix_.size() == 1 && - utils::GetTokenIdFromEncodedValue(encoded_value_for_subword_prefix_[0]) == - unk_token_id_) { - ResetOutputAppendUNK( - sequence_offset_in_text, sequence.size(), original_num_tokens, tokens); - return true; - } - for (int encoded_token_value : encoded_value_for_subword_prefix_) { - AppendTokensToOutput(sequence, - sequence_offset_in_text, - curr_offset_in_sequence, - encoded_token_value, - tokens); - } - return true; -} - -void FastWordPiece::HandleTheRemainingStringOnTriePath( - const std::string& sequence, - int sequence_offset_in_text, - utils::Trie::TraversalCursor* curr_node, - int* original_num_tokens, - int* curr_offset_in_sequence, - std::vector* tokens) const { - if (curr_node->node_id_ == utils::Trie::kRootNodeId) { - return; - } - if (TryHandleContinuingSubWordPrefix(sequence, - sequence_offset_in_text, - *curr_node, - original_num_tokens, - curr_offset_in_sequence, - tokens)) { - *original_num_tokens = tokens->size(); - return; - } - while (curr_node->node_id_ != trie_.GetSuffixRoot() && - curr_node->node_id_ != trie_.GetPuncFailureNode()) { - if (!TryFollowFailureLinkAndCollectTokens(sequence, - sequence_offset_in_text, - curr_offset_in_sequence, - curr_node, - tokens)) { - ResetOutputAppendUNK(sequence_offset_in_text, - sequence.size(), - original_num_tokens, - tokens); - return; - } - } - *original_num_tokens = tokens->size(); -} - -int FastWordPiece::SkipRemainingOfWordAndTrailingWhiteSpaces( - const std::string& sequence, int* curr_idx) const { - int seq_len = sequence.length(); - uint32_t curr_unicode_char; - int end_of_word = *curr_idx; - while (*curr_idx < seq_len) { - auto chwidth = - utils::UTF8ToUInt32(sequence.data() + *curr_idx, &curr_unicode_char); - curr_unicode_char = utils::UTF8ToUnicode(curr_unicode_char); - if (u_isUWhiteSpace(curr_unicode_char)) { - *curr_idx += chwidth; - break; - } - if (utils::IsPunctuationOrChineseChar(curr_unicode_char)) { - break; - } - *curr_idx += chwidth; - end_of_word = *curr_idx; - } - return end_of_word; -} - -std::vector FastWordPiece::TokenizeWithoutPreTokenize( - const std::string& sequence) const { - VLOG(6) << "Using FastWordPiece::TokenizeWithoutPreTokenize to tokenize " - "sequence"; - if (sequence.empty()) { - return {}; - } - std::vector all_tokens; - size_t unicode_len = - utils::GetUnicodeLenFromUTF8(sequence.data(), sequence.length()); - int original_num_tokens = 0; - if (unicode_len > max_input_chars_per_word_) { - ResetOutputAppendUNK(0, sequence.size(), &original_num_tokens, &all_tokens); - } else { - int curr_offset_in_sequence = 0; - auto curr_node = trie_.CreateRootTraversalCursor(); - for (auto ch : sequence) { - while (!trie_.TryTraverseOneStep(&curr_node, ch)) { - if (!TryFollowFailureLinkAndCollectTokens(sequence, - 0, - &curr_offset_in_sequence, - &curr_node, - &all_tokens)) { - ResetOutputAppendUNK( - 0, sequence.size(), &original_num_tokens, &all_tokens); - return all_tokens; - } - } - } - HandleTheRemainingStringOnTriePath(sequence, - 0, - &curr_node, - &original_num_tokens, - &curr_offset_in_sequence, - &all_tokens); - } - if (all_tokens.size() == 0) { - ResetOutputAppendUNK(0, sequence.size(), &original_num_tokens, &all_tokens); - } - VLOG(6) << "All tokens num from TokenizeWithoutPreTokenize: " - << all_tokens.size(); - return all_tokens; -} - -std::vector FastWordPiece::TokenizeWithPreTokenize( - const std::string& sequence) const { - VLOG(6) - << "Using FastWordPiece::TokenizeWithPreTokenize to tokenize sequence"; - // Need to implement - if (sequence.empty()) { - return {}; - } - std::vector all_tokens; - int original_num_tokens = 0; - uint32_t prev_unicode_char, curr_unicode_char; - int curr_idx = 0; - int chwidth = 0; - auto seq_len = sequence.length(); - while (curr_idx < seq_len) { - int curr_offset_in_word = 0; - auto curr_node = trie_.CreateRootTraversalCursor(); - int bytes_length = 0; - int word_offset_in_sequence = curr_idx; - std::string sequence_substr = sequence.substr(curr_idx); - bool fail_to_match = false; - while (curr_idx < seq_len) { - prev_unicode_char = curr_unicode_char; - chwidth = - utils::UTF8ToUInt32(sequence.data() + curr_idx, &curr_unicode_char); - curr_unicode_char = utils::UTF8ToUnicode(curr_unicode_char); - if (bytes_length + chwidth > max_input_chars_per_word_) { - break; - } - std::string curr_substr = sequence.substr(curr_idx, chwidth); - while (!trie_.TryTraverseSeveralSteps(&curr_node, curr_substr)) { - if (!TryFollowFailureLinkAndCollectTokens(sequence_substr, - word_offset_in_sequence, - &curr_offset_in_word, - &curr_node, - &all_tokens)) { - fail_to_match = true; - break; - } - } - if (fail_to_match) { - break; - } - bytes_length += chwidth; - curr_idx += chwidth; - } - if (curr_idx >= seq_len) { - HandleTheRemainingStringOnTriePath(sequence_substr, - word_offset_in_sequence, - &curr_node, - &original_num_tokens, - &curr_offset_in_word, - &all_tokens); - break; - } - bool curr_unicode_char_is_space = u_isUWhiteSpace(curr_unicode_char); - if (curr_unicode_char_is_space || - utils::IsPunctuationOrChineseChar(curr_unicode_char) || - (curr_idx > 0 && - utils::IsPunctuationOrChineseChar(prev_unicode_char))) { - HandleTheRemainingStringOnTriePath( - sequence_substr.substr(0, curr_idx - word_offset_in_sequence), - word_offset_in_sequence, - &curr_node, - &original_num_tokens, - &curr_offset_in_word, - &all_tokens); - if (curr_unicode_char_is_space) { - curr_idx += chwidth; - } - continue; - } - curr_idx += chwidth; - int end_of_word = - SkipRemainingOfWordAndTrailingWhiteSpaces(sequence, &curr_idx); - ResetOutputAppendUNK(word_offset_in_sequence, - end_of_word - word_offset_in_sequence, - &original_num_tokens, - &all_tokens); - } - if (all_tokens.size() == 0) { - ResetOutputAppendUNK(0, sequence.size(), &original_num_tokens, &all_tokens); - } - VLOG(6) << "All tokens num from TokenizeWithPreTokenize: " - << all_tokens.size(); - return all_tokens; -} - -std::vector FastWordPiece::Tokenize(const std::string& sequence) { - if (!with_pretokenization_) { - return TokenizeWithoutPreTokenize(sequence); - } - return TokenizeWithPreTokenize(sequence); -} - -FastWordPiece FastWordPiece::GetFastWordPieceFromFile( - const std::string& file, - const std::string& unk_token, - size_t max_input_chars_per_word, - const std::string& continuing_subword_prefix, - bool with_pretokenization) { - auto vocab = GetVocabFromFile(file); - return FastWordPiece(vocab, - unk_token, - max_input_chars_per_word, - continuing_subword_prefix, - with_pretokenization); -} - -void to_json(nlohmann::json& j, const FastWordPiece& model) { - j = { - {"type", "FastWordPiece"}, - {"vocab", model.vocab_}, - {"unk_token", model.unk_token_}, - {"max_input_chars_per_word", model.max_input_chars_per_word_}, - {"continuing_subword_prefix", model.continuing_subword_prefix_}, - {"with_pretokenization", model.with_pretokenization_}, - }; -} - -void from_json(const nlohmann::json& j, FastWordPiece& model) { - j["vocab"].get_to(model.vocab_); - j["unk_token"].get_to(model.unk_token_); - j["max_input_chars_per_word"].get_to(model.max_input_chars_per_word_); - j["continuing_subword_prefix"].get_to(model.continuing_subword_prefix_); - j["with_pretokenization"].get_to(model.with_pretokenization_); - model.InitFailureAndTrie(); -} - -} // namespace models -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/fast_wordpiece.h b/fast_tokenizer/fast_tokenizer/models/fast_wordpiece.h deleted file mode 100644 index f26534ba6753..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/fast_wordpiece.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2022 TF.Text Authors. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include "fast_tokenizer/models/model.h" -#include "fast_tokenizer/models/wordpiece.h" -#include "fast_tokenizer/utils/failure.h" -#include "fast_tokenizer/utils/trie.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { - -struct FASTTOKENIZER_DECL FastWordPiece : public WordPiece { - FastWordPiece(); - FastWordPiece(const core::Vocab& vocab, - const std::string& unk_token = "[UNK]", - size_t max_input_chars_per_word = 100, - const std::string& continuing_subword_prefix = "##", - bool with_pretokenization = false); - - virtual std::vector Tokenize( - const std::string& sequence) override; - static FastWordPiece GetFastWordPieceFromFile( - const std::string& file, - const std::string& unk_token = "[UNK]", - size_t max_input_chars_per_word = 100, - const std::string& continuing_subword_prefix = "##", - bool with_pretokenization = false); - -private: - void InitFailureAndTrie(); - std::vector TokenizeWithoutPreTokenize( - const std::string& sequence) const; - std::vector TokenizeWithPreTokenize( - const std::string& sequence) const; - bool TryFollowFailureLinkAndCollectTokens( - const std::string& sequence, - int sequence_offset_in_text, - int* curr_offset_in_sequence, - utils::Trie::TraversalCursor* node, - std::vector* tokens) const; - - void AppendTokensToOutput(const std::string& sequence, - int sequence_offset_in_text, - int* curr_offset_in_sequence, - int curr_node_value, - std::vector* tokens) const; - void HandleTheRemainingStringOnTriePath( - const std::string& sequence, - int sequence_offset_in_text, - utils::Trie::TraversalCursor* node, - int* original_num_tokens, - int* curr_offset_in_sequence, - std::vector* tokens) const; - bool TryHandleContinuingSubWordPrefix( - const std::string& sequence, - int sequence_offset_in_text, - const utils::Trie::TraversalCursor& node, - int* original_num_tokens, - int* curr_offset_in_sequence, - std::vector* tokens) const; - void ResetOutputAppendUNK(int sequence_offset_in_text, - int sequence_size, - int* original_num_tokens, - std::vector* tokens) const; - int SkipRemainingOfWordAndTrailingWhiteSpaces(const std::string& sequence, - int* curr_idx) const; - void PrecomputeEncodeValueForSubwordPrefix(); - utils::Trie trie_; - utils::FailureArray failure_array_; - std::vector encoded_value_for_subword_prefix_; - friend void to_json(nlohmann::json& j, const FastWordPiece& model); - friend void from_json(const nlohmann::json& j, FastWordPiece& model); - bool with_pretokenization_; // The end-to-end version of FastWordPiece -}; - -} // namespace models -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/model.h b/fast_tokenizer/fast_tokenizer/models/model.h deleted file mode 100644 index 8a8f8daddf6f..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/model.h +++ /dev/null @@ -1,39 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { - -struct FASTTOKENIZER_DECL Model { - virtual std::vector Tokenize(const std::string& tokens) = 0; - virtual bool TokenToId(const std::string& token, uint32_t* id) const = 0; - virtual bool IdToToken(uint32_t id, std::string* token) const = 0; - virtual core::Vocab GetVocab() const = 0; - virtual size_t GetVocabSize() const = 0; - // Return the saved voacb path - virtual std::vector Save( - const std::string& folder, const std::string& filename_prefix) const = 0; -}; - -} // namespace model -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/models.h b/fast_tokenizer/fast_tokenizer/models/models.h deleted file mode 100644 index feafdd1ae590..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/models.h +++ /dev/null @@ -1,21 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/models/bpe.h" -#include "fast_tokenizer/models/fast_wordpiece.h" -#include "fast_tokenizer/models/model.h" -#include "fast_tokenizer/models/unigram.h" -#include "fast_tokenizer/models/wordpiece.h" \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/models/unigram.cc b/fast_tokenizer/fast_tokenizer/models/unigram.cc deleted file mode 100644 index 255ee1c3ca29..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/unigram.cc +++ /dev/null @@ -1,436 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/models/unigram.h" -#include -#include -#include - -#include "glog/logging.h" -#include "fast_tokenizer/utils/path.h" -#include "fast_tokenizer/utils/unique_ptr.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { - -constexpr float kUnkPenalty = 10.0; - -Unigram::Unigram() { - core::VocabList vocab = {{"", 0.0}}; - std::vector unk_id = {0}; - Init(vocab, unk_id); -} - -Unigram::Unigram(const core::VocabList& vocab, - const std::vector& unk_id) { - Init(vocab, unk_id); -} - -Unigram::Unigram(const Unigram& other) { Init(other.vocab_, other.unk_id_); } - -void Unigram::Init(const core::VocabList& vocab, - const std::vector& unk_id) { - size_t n = vocab.size(); - if (unk_id.size() > 0) { - if (n == 0) { - std::ostringstream oss; - oss << "EmptyVocabulary error occurs when init unigram with unk token."; - throw std::runtime_error(oss.str()); - } else if (unk_id[0] >= n) { - std::ostringstream oss; - oss << "Unk token id is not in vocab when init unigram with unk token."; - throw std::runtime_error(oss.str()); - } - } - - vocab_ = vocab; - unk_id_ = unk_id; - - bos_id_ = n + 1; - eos_id_ = n + 2; - min_score_ = std::numeric_limits::max(); - - std::vector keys; - std::vector values; - // id = 0 is unk_id_ - for (size_t id = 0; id < n; ++id) { - size_t actual_id = id; - token_to_ids_.insert({vocab[id].first, actual_id}); - keys.push_back(vocab[id].first.c_str()); - values.push_back(actual_id); - if (vocab[id].second < min_score_) { - min_score_ = vocab[id].second; - } - } - - std::vector sorted_keys; - std::vector sorted_values; - utils::GetSortedVocab(keys, values, &sorted_keys, &sorted_values); - trie_ = utils::make_unique(); - if (trie_->build(sorted_keys.size(), - const_cast(&sorted_keys[0]), - nullptr, - &sorted_values[0]) != 0) { - std::ostringstream oss; - oss << "Cannot build double-array."; - throw std::runtime_error(oss.str()); - return; - } - // Computes the maximum number of shared prefixes in the trie. - const int kMaxTrieResultsSize = 1024; - std::vector results( - kMaxTrieResultsSize); - trie_results_size_ = 0; - for (size_t id = 0; id < n; ++id) { - const int num_nodes = trie_->commonPrefixSearch(vocab[id].first.data(), - results.data(), - results.size(), - vocab[id].first.size()); - trie_results_size_ = std::max(trie_results_size_, num_nodes); - } - fuse_unk_ = true; - is_optimized_ = true; - if (trie_results_size_ == 0) { - std::ostringstream oss; - oss << "No entry is found in the trie."; - throw std::runtime_error(oss.str()); - } -} - -float Unigram::GetVocabScore(uint32_t id) const { return vocab_.at(id).second; } - -bool Unigram::TokenToId(const std::string& token, uint32_t* id) const { - if (token_to_ids_.find(token) == token_to_ids_.end()) { - return false; - } - *id = token_to_ids_.at(token); - return true; -} - -bool Unigram::IdToToken(uint32_t id, std::string* token) const { - if (id >= vocab_.size()) { - return false; - } - *token = vocab_[id].first; - return true; -} - -core::Vocab Unigram::GetVocab() const { return token_to_ids_; } - -size_t Unigram::GetVocabSize() const { return vocab_.size(); } - -std::vector Unigram::Tokenize(const std::string& sequence) { - std::vector encode_result; - Encode(sequence, &encode_result); - size_t offset = 0; - std::vector tokens; - tokens.reserve(encode_result.size()); - auto UpdateTokens = [&](const std::string& str) { - uint32_t id = 0; - if (token_to_ids_.find(str) != token_to_ids_.end()) { - id = token_to_ids_.at(str); - } else { - if (unk_id_.size() > 0) { - id = unk_id_[0]; - } - } - auto len = str.length(); - tokens.emplace_back(id, str, core::Offset{offset, offset + len}); - offset += len; - }; - - for (auto&& str : encode_result) { - // Avoid to append the filtered_token_ to encoded_result - if (str == filtered_token_) { - offset += filtered_token_.length(); - continue; - } - // Split the tokenized tokens following some regex rule - if (split_rule_ != nullptr) { - re2::StringPiece result; - int start = 0; - int end = str.length(); - while (split_rule_->Match(str, start, end, RE2::UNANCHORED, &result, 1)) { - int curr_start = result.data() - str.data(); - int res_len = result.length(); - start = curr_start + res_len; - std::string result_str(result.data(), res_len); - if (result_str == filtered_token_) { - offset += filtered_token_.length(); - continue; - } - UpdateTokens(result_str); - } - if (start == 0) { - // Hasn't been splitted - UpdateTokens(str); - } - } else { - UpdateTokens(str); - } - } - return tokens; -} - -std::vector Unigram::Save( - const std::string& folder, const std::string& filename_prefix) const { - std::string vocab_path; - if (filename_prefix == "") { - vocab_path = utils::PathJoin(folder, "unigram.json"); - } else { - vocab_path = utils::PathJoin({folder, filename_prefix, "-unigram.json"}); - } - VLOG(6) << "Vocab path" << vocab_path; - std::ofstream fout(vocab_path); - nlohmann::json j = *this; - fout << j.dump(); - fout.close(); - return {vocab_path}; -} - -void Unigram::PopulateNodes(utils::Lattice* lattice) const { - auto get_chars_length = [&lattice](int begin_pos, const char* end) { - int pos = begin_pos; - while (lattice->surface(pos) < end) ++pos; - return pos - begin_pos; - }; - - const float unk_score = min_score_ - kUnkPenalty; - - const int len = lattice->size(); - const char* end = lattice->sentence() + lattice->utf8_size(); - - // +1 just in case. - std::vector trie_results( - trie_results_size_ + 1); - - for (int begin_pos = 0; begin_pos < len; ++begin_pos) { - const char* begin = lattice->surface(begin_pos); - - // Finds all pieces which are prefix of surface(begin_pos). - const size_t num_nodes = - trie_->commonPrefixSearch(begin, - trie_results.data(), - trie_results.size(), - static_cast(end - begin)); - CHECK_LT(num_nodes, trie_results.size()); - - bool has_single_node = false; - - // Inserts pieces to the lattice. - for (size_t k = 0; k < num_nodes; ++k) { - const int length = - get_chars_length(begin_pos, begin + trie_results[k].length); - const int id = trie_results[k].value; - utils::Lattice::Node* node = lattice->Insert(begin_pos, length); - node->id = id; // the value of Trie stores vocab_id. - // User defined symbol receives extra bonus to always be selected. - node->score = vocab_[id].second; - - if (!has_single_node && node->length == 1) { - has_single_node = true; - } - } - - if (!has_single_node) { - if (unk_id_.size() > 0) { - utils::Lattice::Node* node = lattice->Insert(begin_pos, 1); - node->id = unk_id_[0]; // add UNK node. - node->score = unk_score; - } - } - } -} - -void Unigram::Encode(const std::string& normalized, - std::vector* encode_result) { - encode_result->clear(); - if (normalized.empty()) { - return; - } - if (!cache_.GetValue(normalized, encode_result)) { - if (is_optimized_) { - EncodeOptimized(normalized, encode_result); - } else { - EncodeUnoptimized(normalized, encode_result); - } - cache_.SetValue(normalized, *encode_result); - } -} - -void Unigram::EncodeOptimized(const std::string& normalized, - std::vector* encode_result) { - // Represents the last node of the best path. - struct BestPathNode { - int id = -1; // The vocab id. (maybe -1 for UNK) - float best_path_score = - 0; // The total score of the best path ending at this node. - int starts_at = - -1; // The starting position (in utf-8) of this node. The entire best - // path can be constructed by backtracking along this link. - }; - const int size = normalized.size(); - const float unk_score = min_score_ - kUnkPenalty; - // The ends are exclusive. - std::vector best_path_ends_at(size + 1); - // Generate lattice on-the-fly (not stored) and update best_path_ends_at. - int starts_at = 0; - while (starts_at < size) { - std::size_t node_pos = 0; - std::size_t key_pos = starts_at; - const auto best_path_score_till_here = - best_path_ends_at[starts_at].best_path_score; - bool has_single_node = false; - const int mblen = std::min( - utils::OneCharLen(normalized.data() + starts_at), size - starts_at); - while (key_pos < size) { - const int ret = - trie_->traverse(normalized.data(), node_pos, key_pos, key_pos + 1); - if (ret == -2) break; - if (ret >= 0) { - // Update the best path node. - auto& target_node = best_path_ends_at[key_pos]; - const auto length = (key_pos - starts_at); - const auto score = GetVocabScore(ret); - const auto candidate_best_path_score = - score + best_path_score_till_here; - VLOG(4) << "key_pos: " << key_pos; - VLOG(4) << "score: " << score; - VLOG(4) << "best_path_score_till_here: " << best_path_score_till_here; - VLOG(4) << "starts_at: " << starts_at; - VLOG(4) << "token: " << vocab_.at(ret).first; - if (target_node.starts_at == -1 || - candidate_best_path_score > target_node.best_path_score) { - target_node.best_path_score = candidate_best_path_score; - target_node.starts_at = starts_at; - target_node.id = ret; - } - if (!has_single_node && length == mblen) { - has_single_node = true; - } - } - } - if (!has_single_node) { - auto& target_node = best_path_ends_at[starts_at + mblen]; - const auto candidate_best_path_score = - unk_score + best_path_score_till_here; - if (target_node.starts_at == -1 || - candidate_best_path_score > target_node.best_path_score) { - target_node.best_path_score = candidate_best_path_score; - target_node.starts_at = starts_at; - target_node.id = -1; - if (unk_id_.size() > 0) { - target_node.id = unk_id_[0]; - } - } - } - // Move by one unicode character. - starts_at += mblen; - } - int ends_at = size; - std::vector token; - while (ends_at > 0) { - const auto& node = best_path_ends_at[ends_at]; - auto starts_at = node.starts_at; - if (fuse_unk_ && unk_id_.size() > 0 && node.id == unk_id_[0]) { - token.push_back(normalized.substr(starts_at, ends_at - starts_at)); - } else { - if (!token.empty()) { - encode_result->push_back(""); - auto& back = encode_result->back(); - for (int i = token.size() - 1; i >= 0; --i) { - back.append(token[i]); - } - token.clear(); - } - encode_result->push_back( - normalized.substr(starts_at, ends_at - starts_at)); - } - ends_at = node.starts_at; - } - if (!token.empty()) { - encode_result->push_back(""); - auto& back = encode_result->back(); - for (int i = token.size() - 1; i >= 0; --i) { - back.append(token[i]); - } - } - std::reverse(encode_result->begin(), encode_result->end()); -} - -void Unigram::EncodeUnoptimized(const std::string& normalized, - std::vector* encode_result) { - utils::Lattice lattice; - lattice.SetSentence( - utils::simple_string_view(normalized.data(), normalized.size())); - PopulateNodes(&lattice); - if (fuse_unk_) { - std::string token; - for (const auto* node : lattice.Viterbi().first) { - if (unk_id_.size() > 0 && node->id == unk_id_[0]) { - token.append(node->piece.data(), node->piece.size()); - } else { - if (!token.empty()) { - encode_result->push_back(token); - token.clear(); - } - encode_result->push_back(std::string(node->piece.data())); - } - if (!token.empty()) { - encode_result->push_back(token); - } - } - } else { - for (const auto* node : lattice.Viterbi().first) { - encode_result->push_back(std::string(node->piece.data())); - } - } -} - -void Unigram::SetFilterToken(const std::string& filtered_token) { - filtered_token_ = filtered_token; -} - -void Unigram::SetSplitRule(const std::string& split_rule) { - split_rule_ = utils::make_unique(split_rule); -} - -void to_json(nlohmann::json& j, const Unigram& model) { - std::string split_rule = ""; - if (model.split_rule_ != nullptr) { - split_rule = model.split_rule_->pattern(); - } - j = {{"type", "Unigram"}, - {"unk_id", model.unk_id_}, - {"vocab", model.vocab_}, - {"filter_token", model.filtered_token_}, - {"split_rule", split_rule}}; -} - -void from_json(const nlohmann::json& j, Unigram& model) { - std::string filter_token = j.at("filter_token").get(); - std::string split_rule = j.at("split_rule").get(); - model.Init(j.at("vocab").get(), - j.at("unk_id").get>()); - if (!split_rule.empty()) { - model.SetSplitRule(split_rule); - } - model.SetFilterToken(filter_token); -} - -} // namespace model -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/unigram.h b/fast_tokenizer/fast_tokenizer/models/unigram.h deleted file mode 100644 index c66cbbbae5a3..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/unigram.h +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/models/model.h" -#include "fast_tokenizer/utils/cache.h" -#include "fast_tokenizer/utils/lattice.h" -#include "fast_tokenizer/utils/trie.h" - -#include "darts.h" -#include "nlohmann/json.hpp" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { - -struct FASTTOKENIZER_DECL Unigram : public Model { - Unigram(); - Unigram(const core::VocabList& vocab, const std::vector& unk_id); - Unigram(const Unigram& other); - virtual bool TokenToId(const std::string& token, uint32_t* id) const override; - virtual bool IdToToken(uint32_t id, std::string* token) const override; - virtual core::Vocab GetVocab() const override; - virtual size_t GetVocabSize() const override; - virtual std::vector Tokenize( - const std::string& sequence) override; - virtual std::vector Save( - const std::string& folder, - const std::string& filename_prefix) const override; - // Set the filter token for unigram. - void SetFilterToken(const std::string& filtered_token); - // Set the special spliting rule for unigram. - void SetSplitRule(const std::string& split_rule); - -private: - float GetVocabScore(uint32_t id) const; - void Init(const core::VocabList& vocab, const std::vector& unk_id); - void PopulateNodes(utils::Lattice* lattice) const; - void Encode(const std::string& normalized, - std::vector* encode_result); - void EncodeOptimized(const std::string& normalized, - std::vector* encode_result); - void EncodeUnoptimized(const std::string& normalized, - std::vector* encode_result); - - core::Vocab token_to_ids_; - core::VocabList vocab_; - utils::Cache> cache_; - std::unique_ptr trie_; - double min_score_; - std::vector unk_id_; - size_t bos_id_; - size_t eos_id_; - bool fuse_unk_; - bool is_optimized_; - int trie_results_size_; - // Some tokenizer, such as ernie-m, may avoid to append some special - // token to final result, the unigram model doesn't filter any tokens - // by default. - std::string filtered_token_; - // For special rule of token spliting after tokenization, - // the unigram model has no spliting rule by default. - // It's useful for some cases, such as ernie-m tokenizer. - std::unique_ptr split_rule_; - - friend void to_json(nlohmann::json& j, const Unigram& model); - friend void from_json(const nlohmann::json& j, Unigram& model); -}; - -} // namespace models -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/wordpiece.cc b/fast_tokenizer/fast_tokenizer/models/wordpiece.cc deleted file mode 100644 index 079602d12806..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/wordpiece.cc +++ /dev/null @@ -1,294 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include -#include -#include -#include - -#include "fast_tokenizer/models/wordpiece.h" -#include "fast_tokenizer/utils/path.h" -#include "fast_tokenizer/utils/utf8.h" -#include "glog/logging.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { -const std::string WHITESPACE = " \n\r\t\f\v"; - -WordPiece::WordPiece() - : unk_token_("[UNK]"), - continuing_subword_prefix_("##"), - max_input_chars_per_word_(100), - unk_token_id_(0) {} - -WordPiece::WordPiece(const core::Vocab& vocab, - const std::string& unk_token, - size_t max_input_chars_per_word, - const std::string& continuing_subword_prefix, - bool handle_chinese_chars) - : vocab_(vocab), - unk_token_(unk_token), - max_input_chars_per_word_(max_input_chars_per_word), - continuing_subword_prefix_(continuing_subword_prefix), - handle_chinese_chars_(handle_chinese_chars) { - for (const auto& vocab_item : vocab) { - vocab_reversed_[vocab_item.second] = vocab_item.first; - } - unk_token_id_ = vocab.at(unk_token); -} - -// Move version -WordPiece::WordPiece(core::Vocab&& vocab, - std::string&& unk_token, - size_t max_input_chars_per_word, - std::string&& continuing_subword_prefix, - bool handle_chinese_chars) - : vocab_(std::move(vocab)), - unk_token_(std::move(unk_token)), - max_input_chars_per_word_(std::move(max_input_chars_per_word)), - continuing_subword_prefix_(std::move(continuing_subword_prefix)), - handle_chinese_chars_(handle_chinese_chars) { - for (const auto& vocab_item : vocab) { - vocab_reversed_[vocab_item.second] = vocab_item.first; - } - unk_token_id_ = vocab.at(unk_token); -} - -core::Vocab WordPiece::GetVocab() const { return vocab_; } - -size_t WordPiece::GetVocabSize() const { return vocab_.size(); } - -bool WordPiece::TokenToId(const std::string& token, uint32_t* id) const { - if (vocab_.find(token) == vocab_.end()) { - return false; - } - *id = vocab_.at(token); - return true; -} - -bool WordPiece::IdToToken(uint32_t id, std::string* token) const { - if (vocab_reversed_.find(id) == vocab_reversed_.end()) { - return false; - } - *token = vocab_reversed_.at(id); - return true; -} - -std::vector WordPiece::Save( - const std::string& folder, const std::string& filename_prefix) const { - std::string filepath; - if (filename_prefix == "") { - filepath = utils::PathJoin(folder, "vocab.txt"); - } else { - filepath = utils::PathJoin({folder, filename_prefix, "-vocab.txt"}); - } - VLOG(6) << "Full path" << filepath; - std::ofstream fout(filepath); - std::vector> vocab(vocab_.begin(), - vocab_.end()); - std::sort(vocab.begin(), - vocab.end(), - [](const std::pair& left, - const std::pair& right) -> bool { - return left.second < right.second; - }); - for (const auto& vocab_item : vocab) { - fout << vocab_item.first << "\n"; - } - fout.close(); - return {filepath}; -} - -static bool CheckIfStringIsAlphaNum(const std::string& str) { - return std::count_if(str.begin(), str.end(), [](char ch) { - return std::isalnum(ch) > 0; - }) == str.length(); -} - -std::vector WordPiece::Tokenize(const std::string& sequence) { - VLOG(6) << "Using WordPiece::Tokenize to tokenize sequence '" << sequence << "'"; - std::vector all_tokens; - size_t unicode_len = - utils::GetUnicodeLenFromUTF8(sequence.data(), sequence.length()); - if (unicode_len > max_input_chars_per_word_) { - all_tokens.emplace_back( - vocab_.at(unk_token_), unk_token_, core::Offset{0, sequence.length()}); - } else { - bool found_token = true; - uint32_t start = 0; - - while (start < sequence.length()) { - uint32_t end = sequence.length(); - core::Token cur_token; - bool match_cur_token = false; - while (start < end) { - std::string sub_str = sequence.substr(start, end - start); - if (start > 0 && - (handle_chinese_chars_ || CheckIfStringIsAlphaNum(sub_str))) { - sub_str = continuing_subword_prefix_ + sub_str; - } - const auto& vocab_iter = vocab_.find(sub_str); - if (vocab_iter != vocab_.end()) { - cur_token = {vocab_iter->second, sub_str, {start, end}}; - match_cur_token = true; - break; - } - // std::u32string u32sub_str = conv.from_bytes(sub_str); - // end -= utils::GetUTF8CharLen(u32sub_str.back()); - for (auto it = sub_str.rbegin(); it != sub_str.rend(); ++it) { - --end; - if (utils::IsCharBeginBoundary(*it)) { - break; - } - } - } - if (!match_cur_token) { - found_token = false; - break; - } - all_tokens.emplace_back(cur_token); - start = end; - } - - if (!found_token) { - all_tokens.clear(); - all_tokens.emplace_back(vocab_.at(unk_token_), - unk_token_, - core::Offset{0, sequence.length()}); - } - } - return all_tokens; -} - - -core::Vocab WordPiece::GetVocabFromFile(const std::string& file) { - std::ifstream fin(file); - core::Vocab vocab; - int i = 0; - constexpr int MAX_BUFFER_SIZE = 256; - char word[MAX_BUFFER_SIZE]; - while (fin.getline(word, MAX_BUFFER_SIZE)) { - std::string word_str = word; - auto leading_spaces = word_str.find_first_not_of(WHITESPACE); - if (leading_spaces != std::string::npos) { - leading_spaces = (std::min)(leading_spaces, word_str.length() - 1); - word_str = word_str.substr(leading_spaces); - } - auto trailing_spaces = word_str.find_last_not_of(WHITESPACE); - if (trailing_spaces != std::string::npos) { - word_str = word_str.substr(0, trailing_spaces + 1); - } - if (word_str != "") { - vocab[word_str] = i++; - } - } - return vocab; -} - -WordPiece WordPiece::GetWordPieceFromFile( - const std::string& file, - const std::string& unk_token, - size_t max_input_chars_per_word, - const std::string& continuing_subword_prefix) { - auto vocab = GetVocabFromFile(file); - return WordPiece( - vocab, unk_token, max_input_chars_per_word, continuing_subword_prefix); -} - -void to_json(nlohmann::json& j, const WordPiece& model) { - j = { - {"type", "WordPiece"}, - {"vocab", model.vocab_}, - {"unk_token", model.unk_token_}, - {"max_input_chars_per_word", model.max_input_chars_per_word_}, - {"continuing_subword_prefix", model.continuing_subword_prefix_}, - }; -} - -void from_json(const nlohmann::json& j, WordPiece& model) { - j["vocab"].get_to(model.vocab_); - j["unk_token"].get_to(model.unk_token_); - j["max_input_chars_per_word"].get_to(model.max_input_chars_per_word_); - j["continuing_subword_prefix"].get_to(model.continuing_subword_prefix_); -} - - -WordPieceConfig::WordPieceConfig() - : unk_token_("[UNK]"), - max_input_chars_per_word_(100), - continuing_subword_prefix_("##") {} - - -void WordPieceFactory::SetFiles(const std::string& files) { - config_.files_ = files; -} - -void WordPieceFactory::SetUNKToken(const std::string& unk_token) { - config_.unk_token_ = unk_token; -} - -void WordPieceFactory::SetMaxInputCharsPerWord( - size_t max_input_chars_per_word) { - config_.max_input_chars_per_word_ = max_input_chars_per_word; -} - -void WordPieceFactory::SetContinuingSubwordPrefix( - const std::string& continuing_subword_prefix) { - config_.continuing_subword_prefix_ = continuing_subword_prefix; -} - -WordPiece WordPieceFactory::CreateWordPieceModel() { - std::ifstream fin(config_.files_); - if (fin) { - GetVocabFromFiles(config_.files_); - } else { - VLOG(0) << "File " << config_.files_ - << " doesn't exist or can't be accessed."; - config_.vocab_ = core::Vocab(); - } - return WordPiece{config_.vocab_, - config_.unk_token_, - config_.max_input_chars_per_word_, - config_.continuing_subword_prefix_}; -} - -void WordPieceFactory::GetVocabFromFiles(const std::string& files) { - std::ifstream fin(files); - config_.vocab_.clear(); - int i = 0; - constexpr int MAX_BUFFER_SIZE = 256; - char word[MAX_BUFFER_SIZE]; - while (fin.getline(word, MAX_BUFFER_SIZE)) { - std::string word_str = word; - auto leading_spaces = word_str.find_first_not_of(WHITESPACE); - if (leading_spaces != std::string::npos) { - leading_spaces = (std::min)(leading_spaces, word_str.length() - 1); - word_str = word_str.substr(leading_spaces); - } - auto trailing_spaces = word_str.find_last_not_of(WHITESPACE); - if (trailing_spaces != std::string::npos) { - word_str = word_str.substr(0, trailing_spaces + 1); - } - if (word_str != "") { - config_.vocab_[word_str] = i++; - } - } -} - -} // namespace model -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/models/wordpiece.h b/fast_tokenizer/fast_tokenizer/models/wordpiece.h deleted file mode 100644 index 956485522f25..000000000000 --- a/fast_tokenizer/fast_tokenizer/models/wordpiece.h +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/models/model.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace models { - -struct FASTTOKENIZER_DECL WordPiece : public Model { - WordPiece(); - WordPiece(const core::Vocab& vocab, - const std::string& unk_token = "[UNK]", - size_t max_input_chars_per_word = 100, - const std::string& continuing_subword_prefix = "##", - bool handle_chinese_chars = true); - // Move version - WordPiece(core::Vocab&& vocab, - std::string&& unk_token, - size_t max_input_chars_per_word, - std::string&& continuing_subword_prefix, - bool handle_chinese_chars); - virtual std::vector Tokenize( - const std::string& sequence) override; - virtual bool TokenToId(const std::string& token, uint32_t* id) const override; - virtual bool IdToToken(uint32_t id, std::string* token) const override; - virtual core::Vocab GetVocab() const override; - virtual size_t GetVocabSize() const override; - // Return the saved voacb full path - virtual std::vector Save( - const std::string& folder, - const std::string& filename_prefix) const override; - static core::Vocab GetVocabFromFile(const std::string& file); - static WordPiece GetWordPieceFromFile( - const std::string& file, - const std::string& unk_token = "[UNK]", - size_t max_input_chars_per_word = 100, - const std::string& continuing_subword_prefix = "##"); - -protected: - core::Vocab vocab_; - core::VocabReversed vocab_reversed_; - std::string unk_token_; - uint32_t unk_token_id_; - size_t max_input_chars_per_word_; - std::string continuing_subword_prefix_; - bool handle_chinese_chars_; - friend void to_json(nlohmann::json& j, const WordPiece& model); - friend void from_json(const nlohmann::json& j, WordPiece& model); -}; - -struct WordPieceConfig { - WordPieceConfig(); - std::string files_; - core::Vocab vocab_; - std::string unk_token_; - size_t max_input_chars_per_word_; - std::string continuing_subword_prefix_; -}; - - -struct WordPieceFactory { - WordPieceConfig config_; - void SetFiles(const std::string& files); - void SetUNKToken(const std::string& unk_token); - void SetMaxInputCharsPerWord(size_t max_input_chars_per_word); - void SetContinuingSubwordPrefix(const std::string& continuing_subword_prefix); - WordPiece CreateWordPieceModel(); - void GetVocabFromFiles(const std::string& files); -}; - -} // namespace models -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/normalizers/CMakeLists.txt deleted file mode 100644 index b44e4bbcc455..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cc_library(normalizers - SRCS normalizer.cc unicode.cc - utils.cc strip.cc replace.cc bert.cc - precompiled.cc - DEPS re2 json sentencepiece_normalizer icuuc icudata) diff --git a/fast_tokenizer/fast_tokenizer/normalizers/bert.cc b/fast_tokenizer/fast_tokenizer/normalizers/bert.cc deleted file mode 100644 index f4745c449cd8..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/bert.cc +++ /dev/null @@ -1,120 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/normalizers/bert.h" - -#include -#include -#include - -#include "fast_tokenizer/normalizers/strip.h" -#include "fast_tokenizer/normalizers/utils.h" -#include "fast_tokenizer/utils/utils.h" -#include "glog/logging.h" -#include "unicode/uchar.h" -#include "unicode/unistr.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { -BertNormalizer::BertNormalizer(bool clean_text, - bool handle_chinese_chars, - bool strip_accents, - bool lowercase) - : clean_text_(clean_text), - handle_chinese_chars_(handle_chinese_chars), - strip_accents_(strip_accents), - lowercase_(lowercase) {} - -static bool IsControl(int ch) { - if (ch == '\t' || ch == '\n' || ch == '\r') return false; - // It means (general category "C"). - return !u_isprint(ch); -} - -void BertNormalizer::DoCleanText(NormalizedString* input) const { - (*input) - .FilterChar([](char32_t ch) -> bool { - return !(ch == 0 || ch == 0xfffd || IsControl(ch)); - }) - .MapChar([](char32_t ch) -> char32_t { - if (utils::IsWhiteSpace(ch)) { - return ' '; - } - return ch; - }); -} - -void BertNormalizer::DoHandleChineseChars(NormalizedString* input) const { - std::wstring_convert, char32_t> conv; - std::u32string u32input = conv.from_bytes(input->GetStr()); - std::u32string u32output; - std::vector changes; - u32output.reserve(u32input.length() * 3); - changes.reserve(u32input.length() * 3); - for (int i = 0; i < u32input.length(); ++i) { - if (utils::IsChineseChar(u32input[i])) { - u32output.push_back(' '); - u32output.push_back(u32input[i]); - u32output.push_back(' '); - changes.push_back(0); - changes.push_back(1); - changes.push_back(1); - } else { - u32output.push_back(u32input[i]); - changes.push_back(0); - } - } - OffsetMapping new_normalized_offset{u32output, changes}; - input->UpdateNormalized(new_normalized_offset, 0); -} -void BertNormalizer::operator()(NormalizedString* input) const { - if (clean_text_) { - DoCleanText(input); - } - if (handle_chinese_chars_) { - DoHandleChineseChars(input); - } - if (strip_accents_) { - StripAccentsNormalizer()(input); - } - if (lowercase_) { - input->Lowercase(); - } -} - -void to_json(nlohmann::json& j, const BertNormalizer& bert_normalizer) { - j = { - {"type", "BertNormalizer"}, - {"clean_text", bert_normalizer.clean_text_}, - {"handle_chinese_chars", bert_normalizer.handle_chinese_chars_}, - {"strip_accents", bert_normalizer.strip_accents_}, - {"lowercase", bert_normalizer.lowercase_}, - }; -} - -void from_json(const nlohmann::json& j, BertNormalizer& bert_normalizer) { - j.at("clean_text").get_to(bert_normalizer.clean_text_); - j.at("handle_chinese_chars").get_to(bert_normalizer.handle_chinese_chars_); - j.at("lowercase").get_to(bert_normalizer.lowercase_); - if (!j.at("strip_accents").is_null()) { - j.at("strip_accents").get_to(bert_normalizer.strip_accents_); - } else { - bert_normalizer.strip_accents_ = false; - } -} - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/bert.h b/fast_tokenizer/fast_tokenizer/normalizers/bert.h deleted file mode 100644 index 4312bdefb01e..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/bert.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include "nlohmann/json.hpp" -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { -struct FASTTOKENIZER_DECL BertNormalizer : public Normalizer { - BertNormalizer(bool clean_text = true, - bool handle_chinese_chars = true, - bool strip_accents = true, - bool lowercase = true); - virtual void operator()(NormalizedString* input) const override; - BertNormalizer(const BertNormalizer&) = default; - BertNormalizer(BertNormalizer&&) = default; - -private: - bool clean_text_; - bool handle_chinese_chars_; - bool strip_accents_; - bool lowercase_; - void DoCleanText(NormalizedString* input) const; - void DoHandleChineseChars(NormalizedString* input) const; - friend void to_json(nlohmann::json& j, const BertNormalizer& bert_normalizer); - friend void from_json(const nlohmann::json& j, - BertNormalizer& bert_normalizer); -}; -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/normalizer.cc b/fast_tokenizer/fast_tokenizer/normalizers/normalizer.cc deleted file mode 100644 index bc63e0845063..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/normalizer.cc +++ /dev/null @@ -1,656 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include -#include -#include - -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/utils/utf8.h" - -#include "fast_tokenizer/normalizers/unicode.h" -#include "glog/logging.h" -#include "re2/re2.h" -#include "unicode/edits.h" -#include "unicode/errorcode.h" -#include "unicode/normalizer2.h" -#include "unicode/uchar.h" -#include "unicode/utypes.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -NormalizedString::NormalizedString(const std::string& original) - : original_(original), normalized_(original), original_shift_(0) { - // calculate alignments - std::wstring_convert, char32_t> conv; - std::u32string u32normalized = conv.from_bytes(normalized_); - for (int i = 0; i < u32normalized.length(); ++i) { - auto new_normalized_char_len = utils::GetUTF8CharLen(u32normalized[i]); - uint32_t start = 0; - uint32_t end = 0; - if (i != 0) { - start = alignments_.back().second; - } - end = start + new_normalized_char_len; - for (int j = 0; j < new_normalized_char_len; ++j) { - alignments_.push_back({start, end}); - } - } -} - -NormalizedString::NormalizedString(NormalizedString&& other) - : original_(std::move(other.original_)), - normalized_(std::move(other.normalized_)), - alignments_(std::move(other.alignments_)), - original_shift_(other.original_shift_) {} - -NormalizedString& NormalizedString::operator=(NormalizedString&& other) { - original_ = std::move(other.original_); - normalized_ = std::move(other.normalized_); - alignments_ = std::move(other.alignments_); - original_shift_ = other.original_shift_; - return *this; -} - -const std::string& NormalizedString::GetStr() const { return normalized_; } - -const std::string& NormalizedString::GetOrignalStr() const { return original_; } - -uint32_t NormalizedString::GetLen() const { return normalized_.length(); } - -uint32_t NormalizedString::GetOriginalLen() const { return original_.length(); } - -core::Offset NormalizedString::GetOrginalOffset() const { - return {original_shift_, GetOriginalLen() + original_shift_}; -} - -bool NormalizedString::IsEmpty() const { return normalized_.empty(); } - -bool NormalizedString::IsOriginalEmpty() const { return original_.empty(); } - -void NormalizedString::UpdateNormalized(const OffsetMapping& new_normalized, - uint32_t initial_offset) { - UpdateNormalizedRange(new_normalized, initial_offset, {0, GetLen()}, true); -} - -void NormalizedString::UpdateNormalizedRange( - const OffsetMapping& new_normalized, - uint32_t initial_offset, - const core::Range& range, - bool origin_range) { - auto n_range = range; - if (origin_range) { - ConvertOffsets(&n_range, origin_range); - } - // Retrieve the original characters that are being replaced. This let us - // compute the change in byte sizes along the way. - std::wstring_convert, char32_t> conv; - n_range.first = (std::min)(n_range.first, - static_cast(normalized_.length() - 1)); - std::u32string u32replaced_normalized = conv.from_bytes( - normalized_.substr(n_range.first, n_range.second - n_range.first)); - uint32_t initial_removed = 0; - // calculate initial_removed - for (int i = 0; i < initial_offset; ++i) { - size_t chwidth = utils::GetUTF8CharLen(u32replaced_normalized[i]); - initial_removed += chwidth; - } - - uint32_t offset = initial_removed + n_range.first; - std::vector alignments; - alignments.reserve(n_range.second - n_range.first); - - int replaced_normalized_idx = initial_removed; - // Calculate the new alignments - for (int i = 0; i < new_normalized.u32normalized.length(); ++i) { - auto idx = offset; - core::Range align; - int curr_changes = new_normalized.changes[i]; - if (curr_changes > 0) { - // Insert a char - if (idx < 1) { - align = {0, 0}; - } else { - align = alignments_[idx - 1]; - } - } else { - align = alignments_[idx]; - } - char32_t new_normalized_char = new_normalized.u32normalized[i]; - auto new_normalized_char_len = utils::GetUTF8CharLen(new_normalized_char); - char32_t replaced_char = -1; - if (curr_changes <= 0) { - replaced_char = u32replaced_normalized[replaced_normalized_idx++]; - } - uint32_t replaced_char_size = - (replaced_char == -1) ? 0 : utils::GetUTF8CharLen(replaced_char); - - uint32_t total_bytes_to_remove = 0; - if (curr_changes < 0) { - for (int j = 0; j < -curr_changes; ++j) { - replaced_char = u32replaced_normalized[replaced_normalized_idx++]; - total_bytes_to_remove += utils::GetUTF8CharLen(replaced_char); - } - } - offset += replaced_char_size + total_bytes_to_remove; - alignments.insert(alignments.end(), new_normalized_char_len, align); - } - // Replace the old alignments in n_range - if (n_range.second - n_range.first >= alignments.size()) { - std::memcpy(alignments_.data() + n_range.first, - alignments.data(), - alignments.size() * sizeof(core::Range)); - alignments_.erase(alignments_.begin() + n_range.first + alignments.size(), - alignments_.begin() + n_range.second); - } else { - std::vector new_alignments; - auto third_len = 0; - if (alignments_.size() > n_range.second) { - third_len = alignments_.size() - n_range.second; - } - new_alignments.resize(n_range.first + alignments.size() + third_len); - if (n_range.first > 0) { - std::copy_n(alignments_.begin(), n_range.first, new_alignments.begin()); - } - std::copy_n(alignments.begin(), - alignments.size(), - new_alignments.begin() + n_range.first); - if (third_len > 0) { - std::copy_n(alignments_.begin() + n_range.second, - third_len, - new_alignments.begin() + n_range.first + alignments.size()); - } - alignments_ = std::move(new_alignments); - } - // Unicode -> UTF8 - uint32_t normalized_utf8_size = 0; - for (auto& ch : new_normalized.u32normalized) { - normalized_utf8_size += utils::GetUTF8CharLen(ch); - } - std::vector utf8_str(normalized_utf8_size + 1); - utils::GetUTF8Str(new_normalized.u32normalized.data(), - utf8_str.data(), - new_normalized.u32normalized.length()); - - // Update normalized_ - auto normalized_iter = normalized_.begin(); - normalized_.replace(normalized_iter + n_range.first, - normalized_iter + n_range.second, - utf8_str.data(), - normalized_utf8_size); -} - -bool NormalizedString::ConvertOffsets(core::Range* range, - bool origin_range) const { - auto len_original = GetOriginalLen(); - auto len_normalized = GetLen(); - if (range->first == range->second) { - return true; - } - if (range->first > range->second) { - return false; - } - if (origin_range && original_.empty() && - (range->first == 0 && range->second == 0)) { - range->second = len_normalized; - return true; - } - if (!origin_range && normalized_.empty() && - (range->first == 0 && range->second == 0)) { - range->second = len_original; - return true; - } - if (origin_range) { - int start = -1; - int end = -1; - for (int i = 0; i < alignments_.size(); ++i) { - if (range->second >= alignments_[i].second) { - if (start < 0 && range->first <= alignments_[i].first) { - if (alignments_[i].first != alignments_[i].second) { - start = i; - } - } - if (range->second >= alignments_[i].second) { - end = i + 1; - } - } - } - if (start > 0 && end < 0) { - *range = {start, start}; - } else if (start < 0 && end > 0) { - *range = {end, end}; - } else if (start > 0 && end > 0) { - *range = {start, end}; - } else { - return false; - } - } else { - range->first = alignments_[range->first].first; - range->second = alignments_[range->second - 1].second; - } - return true; -} - -void NormalizedString::RunNormalization(const std::string& mode) { - icu::ErrorCode icu_error; - const icu::Normalizer2* normalizer = nullptr; - if (mode == "NFD") { - normalizer = icu::Normalizer2::getNFDInstance(icu_error); - } else if (mode == "NFKD") { - normalizer = icu::Normalizer2::getNFKDInstance(icu_error); - } else if (mode == "NFC") { - normalizer = icu::Normalizer2::getNFCInstance(icu_error); - } else if (mode == "NFKC") { - normalizer = icu::Normalizer2::getNFKCInstance(icu_error); - } - std::string normalized_result; - icu::Edits edits; - icu::StringByteSink byte_sink(&normalized_result); - normalizer->normalizeUTF8( - 0, - icu::StringPiece(normalized_.data(), normalized_.size()), - byte_sink, - &edits, - icu_error); - std::wstring_convert, char32_t> conv; - std::u32string u32new_normalized = conv.from_bytes(normalized_result); - // Set changes - std::vector changes; - changes.reserve(u32new_normalized.length()); - auto iter = edits.getFineIterator(); - int old_offset = 0; - int new_offset = 0; - // The edits record the byte level modification, so need to transform to char - // level - // using GetUnicodeLenFromUTF8 - while (iter.next(icu_error)) { - auto old_length = iter.oldLength(); - auto new_length = iter.newLength(); - auto new_unicode_len = utils::GetUnicodeLenFromUTF8( - normalized_result.data() + new_offset, new_length); - auto old_unicode_len = utils::GetUnicodeLenFromUTF8( - normalized_.data() + old_offset, old_length); - old_offset += old_length; - new_offset += new_length; - if (old_unicode_len == new_unicode_len) { - // Just replace the char - changes.insert(changes.end(), old_unicode_len, 0); - } else if (old_unicode_len < new_unicode_len) { - // Insert the char - changes.insert(changes.end(), old_unicode_len, 0); - changes.insert(changes.end(), new_unicode_len - old_unicode_len, 1); - } else /* old_length > new_length */ { - // Remove the char - if (new_unicode_len > 1) { - changes.insert(changes.end(), new_unicode_len - 1, 0); - } - changes.push_back(new_unicode_len - old_unicode_len); - } - } - OffsetMapping new_normalized_offset{u32new_normalized, changes}; - // Update normalized_ and alignments_ - UpdateNormalized(new_normalized_offset, 0); -} - -NormalizedString& NormalizedString::NFD() { - RunNormalization("NFD"); - return *this; -} - -NormalizedString& NormalizedString::NFKD() { - RunNormalization("NFKD"); - return *this; -} - -NormalizedString& NormalizedString::NFC() { - RunNormalization("NFC"); - return *this; -} - -NormalizedString& NormalizedString::NFKC() { - RunNormalization("NFKC"); - return *this; -} - -NormalizedString& NormalizedString::LStrip() { return LRStrip(true, false); } - -NormalizedString& NormalizedString::RStrip() { return LRStrip(false, true); } - -const std::string WHITESPACE = " \n\r\t\f\v"; - -NormalizedString& NormalizedString::LRStrip(bool left, bool right) { - uint32_t leading_spaces = 0; - uint32_t trailing_spaces = 0; - std::string new_normalized = normalized_; - if (left) { - leading_spaces = new_normalized.find_first_not_of(WHITESPACE); - if (leading_spaces != std::string::npos) { - leading_spaces = (std::min)( - leading_spaces, static_cast(new_normalized.length() - 1)); - new_normalized = new_normalized.substr(leading_spaces); - } - } - if (right) { - trailing_spaces = new_normalized.find_last_not_of(WHITESPACE); - if (trailing_spaces != std::string::npos) { - new_normalized = new_normalized.substr(0, trailing_spaces + 1); - } - } - - std::wstring_convert, char32_t> conv; - std::u32string u32new_normalized = conv.from_bytes(new_normalized); - // Set changes - std::vector changes(u32new_normalized.length(), 0); - changes.back() = -trailing_spaces; - - OffsetMapping new_normalized_offset{u32new_normalized, changes}; - // Update normalized_ and alignments_ - UpdateNormalized(new_normalized_offset, leading_spaces); - return *this; -} - -NormalizedString& NormalizedString::FilterChar( - std::function keep_char_fn) { - std::wstring_convert, char32_t> conv; - std::u32string u32new_normalized; - u32new_normalized.reserve(normalized_.length()); - uint32_t removed_start = 0; - uint32_t removed = 0; - std::vector changes; - changes.reserve(normalized_.length()); - bool has_init_ch = false; - uint32_t last_char; - uint32_t curr_char; - size_t utf8_len = 0; - while (utf8_len < normalized_.length()) { - auto chwidth = - utils::UTF8ToUInt32(normalized_.data() + utf8_len, &curr_char); - curr_char = utils::UTF8ToUnicode(curr_char); - if (keep_char_fn(curr_char)) { - if (has_init_ch) { - u32new_normalized.push_back(last_char); - changes.push_back(-removed); - } else { - has_init_ch = true; - removed_start = removed; - } - last_char = curr_char; - removed = 0; - } else { - removed += 1; - } - utf8_len += chwidth; - } - if (has_init_ch) { - u32new_normalized.push_back(last_char); - changes.push_back(-removed); - } - OffsetMapping new_normalized_offset{u32new_normalized, changes}; - // Update normalized_ and alignments_ - UpdateNormalized(new_normalized_offset, removed_start); - return *this; -} - -NormalizedString& NormalizedString::MapChar( - std::function map_char_fn) { - size_t utf8_len = 0; - std::u32string u32normalized; - uint32_t curr_char; - u32normalized.reserve(normalized_.length()); - while (utf8_len < normalized_.length()) { - auto chwidth = - utils::UTF8ToUInt32(normalized_.data() + utf8_len, &curr_char); - curr_char = utils::UTF8ToUnicode(curr_char); - curr_char = map_char_fn(curr_char); - u32normalized.push_back(curr_char); - utf8_len += chwidth; - } - std::vector changes(u32normalized.size(), 0); - UpdateNormalized({u32normalized, changes}, 0); - return *this; -} - -NormalizedString& NormalizedString::Lowercase() { - std::wstring_convert, char32_t> conv; - std::u32string u32normalized = conv.from_bytes(normalized_); - // Can cover all single char covert cases - for (int i = 0; i < u32normalized.length(); ++i) { - u32normalized[i] = u_tolower(u32normalized[i]); - } - // No need to update normalized range - normalized_ = conv.to_bytes(u32normalized); - return *this; -} - -NormalizedString& NormalizedString::Replace(const re2::RE2& pattern, - const std::string& content) { - re2::StringPiece result; - size_t start = 0; - size_t end = normalized_.length(); - int64_t offset = 0; - - std::u32string u32content; - u32content.reserve(content.size()); - std::vector changes; - changes.reserve(content.size()); - - size_t content_utf8_len = 0; - while (content_utf8_len < content.length()) { - uint32_t content_char; - auto content_char_width = - utils::UTF8ToUInt32(content.data() + content_utf8_len, &content_char); - content_char = utils::UTF8ToUnicode(content_char); - u32content.push_back(content_char); - changes.push_back(1); - content_utf8_len += content_char_width; - } - size_t new_len = content.length(); - - OffsetMapping new_normalized{u32content, changes}; - - while (pattern.Match(normalized_, start, end, RE2::UNANCHORED, &result, 1)) { - size_t curr_start = result.data() - normalized_.data(); - size_t old_len = result.length(); - size_t curr_end = curr_start + old_len; - size_t removed_chars = - utils::GetUnicodeLenFromUTF8(normalized_.data() + curr_start, old_len); - UpdateNormalizedRange( - new_normalized, removed_chars, {curr_start, curr_end}, false); - offset = new_len - old_len; - // update start - start = curr_end; - if (offset >= 0) { - start = curr_end + offset; - } else { - size_t uoffset = -offset; - start = (curr_end >= uoffset) ? curr_end - uoffset : 0; - } - end = normalized_.length(); - } - return *this; -} - -NormalizedString& NormalizedString::Prepend(const std::string& content) { - // Get the first unicode char of normalized - uint32_t first_char_of_normalized; - auto first_char_width = - utils::UTF8ToUInt32(normalized_.data(), &first_char_of_normalized); - first_char_of_normalized = utils::UTF8ToUnicode(first_char_of_normalized); - - std::u32string u32content; - u32content.reserve(content.length()); - std::vector changes; - changes.reserve(content.length()); - uint32_t utf8_len = 0; - while (utf8_len < content.length()) { - uint32_t content_char; - auto content_char_width = - utils::UTF8ToUInt32(content.data() + utf8_len, &content_char); - content_char = utils::UTF8ToUnicode(content_char); - u32content.push_back(content_char); - if (utf8_len == 0) { - changes.push_back(0); - } else { - changes.push_back(1); - } - utf8_len += content_char_width; - } - u32content.push_back(first_char_of_normalized); - changes.push_back(1); - UpdateNormalizedRange({u32content, changes}, 0, {0, first_char_width}, false); - return *this; -} - -bool NormalizedString::ValidateRange(const core::Range& range, - bool origin_range) const { - if (origin_range) { - return utils::IsCharBoundary(original_.data() + range.first) && - utils::IsCharBoundary(original_.data() + range.second - 1); - } - return utils::IsCharBoundary(normalized_.data() + range.first) && - utils::IsCharBoundary(normalized_.data() + range.second - 1); -} - -bool NormalizedString::Slice(core::Range range, - NormalizedString* normalized, - bool origin_range) const { - if (ValidateRange(range, origin_range)) { - core::Range normalized_range = range; - core::Range original_range = range; - if (origin_range) { - ConvertOffsets(&normalized_range, true); - } else { - ConvertOffsets(&original_range, false); - } - uint32_t n_shift = original_range.first; - - original_range.first = - (std::min)(original_range.first, - static_cast(this->original_.length() - 1)); - normalized->original_ = this->original_.substr( - original_range.first, original_range.second - original_range.first); - - normalized_range.first = - (std::min)(normalized_range.first, - static_cast(this->normalized_.length() - 1)); - normalized->normalized_ = this->normalized_.substr( - normalized_range.first, - normalized_range.second - normalized_range.first); - normalized->alignments_.reserve(normalized_range.second - - normalized_range.first); - for (uint32_t i = normalized_range.first; i < normalized_range.second; - ++i) { - normalized->alignments_.emplace_back( - this->alignments_[i].first - n_shift, - this->alignments_[i].second - n_shift); - } - - normalized->original_shift_ = this->original_shift_ + original_range.first; - return true; - } - return false; -} - -uint32_t NormalizedString::GetMatch( - const std::string& normalized, - const re2::RE2& pattern, - std::vector>* matches, - bool invert) const { - size_t start = 0; - size_t end = normalized.length(); - // Construct the matches whose mode is REMOVED. - re2::StringPiece result; - uint32_t reserved_num = 0; - while (pattern.Match(normalized, start, end, RE2::UNANCHORED, &result, 1)) { - size_t curr_start = result.data() - normalized.data(); - size_t curr_end = curr_start + result.length(); - if (start != curr_start) { - matches->push_back({{start, curr_start}, invert}); - if (!invert) { - ++reserved_num; - } - } - matches->push_back({{curr_start, curr_end}, !invert}); - if (invert) { - ++reserved_num; - } - start = curr_end; - } - if (start < end) { - matches->push_back({{start, end}, invert}); - if (!invert) { - ++reserved_num; - } - } - return reserved_num; -} - -uint32_t NormalizedString::GetMatch( - const std::string& normalized, - const std::function& pattern_func, - std::vector>* matches, - bool invert) const { - size_t utf8_len = 0; - size_t start = 0; - size_t curr_start = 0; - size_t curr_end = 0; - matches->reserve(normalized.length()); - uint32_t ch; - uint32_t reserved_num = 0; - while (utf8_len < normalized.length()) { - auto chwidth = utils::UTF8ToUInt32(normalized.data() + utf8_len, &ch); - ch = utils::UTF8ToUnicode(ch); - if (pattern_func(ch)) { - curr_start = utf8_len; - curr_end = curr_start + chwidth; - if (curr_start != start) { - matches->emplace_back(core::Range{start, curr_start}, invert); - if (!invert) { - ++reserved_num; - } - } - matches->emplace_back(core::Range{curr_start, curr_end}, !invert); - if (invert) { - ++reserved_num; - } - start = curr_end; - } - utf8_len += chwidth; - } - if (start < normalized.length()) { - matches->emplace_back(core::Range{start, normalized.length()}, invert); - if (!invert) { - ++reserved_num; - } - } - return reserved_num; -} - -template void NormalizedString::Split(const re2::RE2& pattern, - core::SplitMode mode, - std::vector* normalizes, - bool invert) const; -template void NormalizedString::Split( - const std::function& pattern_func, - core::SplitMode mode, - std::vector* normalizes, - bool invert) const; - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/normalizer.h b/fast_tokenizer/fast_tokenizer/normalizers/normalizer.h deleted file mode 100644 index 9a8b74e687cb..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/normalizer.h +++ /dev/null @@ -1,205 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/utils/utils.h" - -namespace re2 { -class RE2; -} // namespace re2 - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -struct FASTTOKENIZER_DECL OffsetMapping { - std::u32string u32normalized; - std::vector changes; // Same size as normalized -}; - -class FASTTOKENIZER_DECL NormalizedString { -public: - NormalizedString(const std::string& original); - NormalizedString(NormalizedString&& other); - NormalizedString(const NormalizedString& other) = default; - NormalizedString& operator=(const NormalizedString& other) = default; - NormalizedString& operator=(NormalizedString&& other); - const std::string& GetStr() const; - const std::string& GetOrignalStr() const; - uint32_t GetLen() const; - uint32_t GetOriginalLen() const; - core::Offset GetOrginalOffset() const; - bool IsEmpty() const; - bool IsOriginalEmpty() const; - - // Unicode Normalization - NormalizedString& NFD(); - NormalizedString& NFKD(); - NormalizedString& NFC(); - NormalizedString& NFKC(); - - // Strip - NormalizedString& LRStrip(bool left, bool right); - NormalizedString& LStrip(); - NormalizedString& RStrip(); - - NormalizedString& FilterChar(std::function keep_char_fn); - NormalizedString& MapChar(std::function map_char_fn); - NormalizedString& Lowercase(); - NormalizedString& Replace(const re2::RE2& pattern, - const std::string& content); - NormalizedString& Prepend(const std::string& content); - bool Slice(core::Range range, - NormalizedString* normalized, - bool origin_range) const; - - void UpdateNormalized(const OffsetMapping& new_normalized, - uint32_t initial_offset); - template - void Split(const PatternType& - pattern, /* re2::RE2 or std::function */ - core::SplitMode mode, - std::vector* normalizes, - bool invert = false) const { - // Vec<(Offsets, should_remove)> - std::vector> matches; - auto normalizes_size = GetMatch(normalized_, pattern, &matches, invert); - // Convert matches - switch (mode) { - case core::SplitMode::REMOVED: - break; - case core::SplitMode::ISOLATED: { - for (auto& match : matches) { - match.second = false; - } - normalizes_size = matches.size(); - break; - } - case core::SplitMode::MERGED_WITH_PREVIOUS: { - bool previous_match = false; - std::vector> new_matches; - for (const auto& match : matches) { - auto offset = match.first; - bool curr_match = match.second; - if (curr_match && !previous_match) { - if (new_matches.size() > 0) { - new_matches.back().first.second = offset.second; - } else { - new_matches.push_back({offset, false}); - } - } else { - new_matches.push_back({offset, false}); - } - previous_match = curr_match; - } - matches = std::move(new_matches); - normalizes_size = matches.size(); - break; - } - case core::SplitMode::MERGED_WITH_NEXT: { - bool previous_match = false; - std::vector> new_matches; - for (auto it = matches.crbegin(); it != matches.crend(); ++it) { - const auto& match = *it; - auto offset = match.first; - bool curr_match = match.second; - if (curr_match && !previous_match) { - if (new_matches.size() > 0) { - new_matches.back().first.first = offset.first; - } else { - new_matches.push_back({offset, false}); - } - } else { - new_matches.push_back({offset, false}); - } - previous_match = curr_match; - } - matches = std::move(new_matches); - normalizes_size = matches.size(); - std::reverse(matches.begin(), matches.end()); - break; - } - case core::SplitMode::CONTIGUOUS: { - bool previous_match = false; - std::vector> new_matches; - for (const auto& match : matches) { - auto offset = match.first; - bool curr_match = match.second; - if (curr_match == previous_match) { - if (new_matches.size() > 0) { - new_matches.back().first.second = offset.second; - } else { - new_matches.push_back({offset, false}); - } - } else { - new_matches.push_back({offset, false}); - } - previous_match = curr_match; - } - matches = std::move(new_matches); - normalizes_size = matches.size(); - break; - } - default: - break; - } - normalizes->resize(normalizes_size); - int idx = 0; - for (const auto& match : matches) { - if (!match.second) { - Slice(match.first, &(normalizes->at(idx++)), false); - } - } - } - bool ConvertOffsets(core::Range* range, bool origin_range = true) const; - NormalizedString() = default; - -private: - std::string original_; - std::string normalized_; - // In order to keep track of the offset mapping from - // original_ to normalized_ - std::vector alignments_; - uint32_t original_shift_; - - void UpdateNormalizedRange(const OffsetMapping& new_normalized, - uint32_t initial_offset, - const core::Range& range, - bool origin_range = true); - void RunNormalization(const std::string& mode); - bool ValidateRange(const core::Range& range, bool origin_range) const; - - uint32_t GetMatch(const std::string& normalized, - const re2::RE2& pattern, - std::vector>* matches, - bool invert = false) const; - - uint32_t GetMatch(const std::string& normalized, - const std::function& pattern_func, - std::vector>* matches, - bool invert = false) const; -}; - -struct FASTTOKENIZER_DECL Normalizer { - virtual void operator()(NormalizedString* mut_str) const = 0; -}; - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/normalizers.h b/fast_tokenizer/fast_tokenizer/normalizers/normalizers.h deleted file mode 100644 index 6f29e0c2eb25..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/normalizers.h +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/normalizers/precompiled.h" -#include "fast_tokenizer/normalizers/replace.h" -#include "fast_tokenizer/normalizers/strip.h" -#include "fast_tokenizer/normalizers/unicode.h" -#include "fast_tokenizer/normalizers/utils.h" diff --git a/fast_tokenizer/fast_tokenizer/normalizers/precompiled.cc b/fast_tokenizer/fast_tokenizer/normalizers/precompiled.cc deleted file mode 100644 index 7d5189d30f26..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/precompiled.cc +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/normalizers/precompiled.h" -#include -#include - -#include "glog/logging.h" -#include "fast_tokenizer/utils/unique_ptr.h" - - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -PrecompiledNormalizer::PrecompiledNormalizer( - const std::string& precompiled_charsmap) { - SetPrecompiledCharsMap(precompiled_charsmap); -} - -PrecompiledNormalizer::PrecompiledNormalizer( - const PrecompiledNormalizer& precompiled_normalizer) - : sentencepiece_normalizer_(new utils::Normalizer( - *precompiled_normalizer.sentencepiece_normalizer_.get())) {} - -static std::string GetByteFromString(const std::string& str) { - std::ostringstream oss; - oss << std::hex << std::setfill('0'); - for (int i = 0; i < str.length(); ++i) { - oss << "\\x" << std::setw(2) << (static_cast(str[i]) & 0xFF); - } - return oss.str(); -} - -void PrecompiledNormalizer::SetPrecompiledCharsMap( - const std::string& precompiled_charsmap) { - sentencepiece_normalizer_ = - utils::make_unique(precompiled_charsmap); -} - -void PrecompiledNormalizer::operator()(NormalizedString* mut_str) const { - std::string normalized; - std::vector norm_to_orig; - std::u32string u32content; - if (sentencepiece_normalizer_->Normalize(mut_str->GetStr().data(), - mut_str->GetStr().length(), - &normalized, - &norm_to_orig, - &u32content)) { - mut_str->UpdateNormalized({u32content, norm_to_orig}, 0); - } -} - -void to_json(nlohmann::json& j, - const PrecompiledNormalizer& precompiled_normalizer) { - const auto& precompiled_str = - precompiled_normalizer.sentencepiece_normalizer_ - ->GetPrecompiledCharsmap(); - std::vector bytes(precompiled_str.begin(), precompiled_str.end()); - j = {{"type", "PrecompiledNormalizer"}, {"precompiled_charsmap", bytes}}; -} - -void from_json(const nlohmann::json& j, - PrecompiledNormalizer& precompiled_normalizer) { - std::vector bytes; - j.at("precompiled_charsmap").get_to(bytes); - std::ostringstream precompiled_charsmap_oss; - for (int i = 0; i < bytes.size(); ++i) { - precompiled_charsmap_oss << static_cast(bytes[i]); - } - precompiled_normalizer.SetPrecompiledCharsMap(precompiled_charsmap_oss.str()); -} - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/precompiled.h b/fast_tokenizer/fast_tokenizer/normalizers/precompiled.h deleted file mode 100644 index 3641952030f6..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/precompiled.h +++ /dev/null @@ -1,44 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include "nlohmann/json.hpp" -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/utils/sentencepiece_normalizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { - -namespace normalizers { -struct FASTTOKENIZER_DECL PrecompiledNormalizer : public Normalizer { - PrecompiledNormalizer() = default; - explicit PrecompiledNormalizer(const std::string& precompiled_charsmap); - PrecompiledNormalizer(const PrecompiledNormalizer& precompiled_normalizer); - - virtual void operator()(NormalizedString* mut_str) const override; - void SetPrecompiledCharsMap(const std::string& precompiled_charsmap); - -private: - std::unique_ptr sentencepiece_normalizer_; - friend void to_json(nlohmann::json& j, - const PrecompiledNormalizer& precompiled_normalizer); - friend void from_json(const nlohmann::json& j, - PrecompiledNormalizer& precompiled_normalizer); -}; -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/replace.cc b/fast_tokenizer/fast_tokenizer/normalizers/replace.cc deleted file mode 100644 index 1d7f81d09a5f..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/replace.cc +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/normalizers/replace.h" -#include "fast_tokenizer/utils/unique_ptr.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -ReplaceNormalizer::ReplaceNormalizer(const std::string& pattern, - const std::string& content) - : pattern_(new re2::RE2(pattern)), content_(content) {} - -ReplaceNormalizer::ReplaceNormalizer( - const ReplaceNormalizer& replace_normalizer) - : pattern_(new re2::RE2(replace_normalizer.pattern_->pattern())), - content_(replace_normalizer.content_) {} - -void ReplaceNormalizer::operator()(NormalizedString* input) const { - input->Replace(*pattern_, content_); -} - -void to_json(nlohmann::json& j, const ReplaceNormalizer& replace_normalizer) { - j = { - {"type", "ReplaceNormalizer"}, - {"pattern", replace_normalizer.pattern_->pattern()}, - {"content", replace_normalizer.content_}, - }; -} - -void from_json(const nlohmann::json& j, ReplaceNormalizer& replace_normalizer) { - replace_normalizer.pattern_ = - utils::make_unique(j.at("pattern").get()); - j.at("content").get_to(replace_normalizer.content_); -} - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/replace.h b/fast_tokenizer/fast_tokenizer/normalizers/replace.h deleted file mode 100644 index 76141f7669c8..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/replace.h +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include "nlohmann/json.hpp" -#include "fast_tokenizer/normalizers/normalizer.h" -#include "re2/re2.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -struct FASTTOKENIZER_DECL ReplaceNormalizer : public Normalizer { - ReplaceNormalizer() = default; - ReplaceNormalizer(const std::string& pattern, const std::string& content); - ReplaceNormalizer(const ReplaceNormalizer& replace_normalizer); - virtual void operator()(NormalizedString* mut_str) const override; - friend void to_json(nlohmann::json& j, - const ReplaceNormalizer& replace_normalizer); - friend void from_json(const nlohmann::json& j, - ReplaceNormalizer& replace_normalizer); - -private: - std::unique_ptr pattern_; - std::string content_; -}; - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/strip.cc b/fast_tokenizer/fast_tokenizer/normalizers/strip.cc deleted file mode 100644 index c14c23f27164..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/strip.cc +++ /dev/null @@ -1,68 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/normalizers/strip.h" -#include "unicode/translit.h" -#include "unicode/unistr.h" -#include "unicode/utypes.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { -StripNormalizer::StripNormalizer(bool left /* = true*/, bool right /* = true*/) - : left_(left), right_(right) {} - -void StripNormalizer::operator()(NormalizedString* input) const { - if (left_) { - input->LStrip(); - } - if (right_) { - input->RStrip(); - } -} - -void to_json(nlohmann::json& j, const StripNormalizer& strip_normalizer) { - j = { - {"type", "StripNormalizer"}, - {"left", strip_normalizer.left_}, - {"right", strip_normalizer.right_}, - }; -} - -void from_json(const nlohmann::json& j, StripNormalizer& strip_normalizer) { - j.at("left").get_to(strip_normalizer.left_); - j.at("right").get_to(strip_normalizer.right_); -} - -void StripAccentsNormalizer::operator()(NormalizedString* input) const { - input->NFD(); - input->FilterChar([](char32_t ch) -> bool { - // equals to `unicodedata.category(char) == 'Mn'` - return u_charType(ch) != U_NON_SPACING_MARK; - }); -} - -void to_json(nlohmann::json& j, - const StripAccentsNormalizer& strip_normalizer) { - j = { - {"type", "StripAccentsNormalizer"}, - }; -} - -void from_json(const nlohmann::json& j, - StripAccentsNormalizer& strip_normalizer) {} - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/strip.h b/fast_tokenizer/fast_tokenizer/normalizers/strip.h deleted file mode 100644 index e8af13ac36a6..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/strip.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include - -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -struct FASTTOKENIZER_DECL StripNormalizer : public Normalizer { - StripNormalizer(bool left = true, bool right = true); - virtual void operator()(NormalizedString* input) const override; - StripNormalizer(StripNormalizer&&) = default; - StripNormalizer(const StripNormalizer&) = default; - -private: - bool left_; - bool right_; - friend void to_json(nlohmann::json& j, - const StripNormalizer& strip_normalizer); - friend void from_json(const nlohmann::json& j, - StripNormalizer& strip_normalizer); -}; - -struct FASTTOKENIZER_DECL StripAccentsNormalizer : public Normalizer { - virtual void operator()(NormalizedString* input) const override; - friend void to_json(nlohmann::json& j, - const StripAccentsNormalizer& strip_normalizer); - friend void from_json(const nlohmann::json& j, - StripAccentsNormalizer& strip_normalizer); -}; - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/unicode.cc b/fast_tokenizer/fast_tokenizer/normalizers/unicode.cc deleted file mode 100644 index 5c16c0d00eae..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/unicode.cc +++ /dev/null @@ -1,103 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include -#include - -#include "fast_tokenizer/normalizers/unicode.h" -#include "unicode/edits.h" -#include "unicode/errorcode.h" -#include "unicode/normalizer2.h" -#include "unicode/utypes.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -void NFCNormalizer::operator()(NormalizedString* input) const { input->NFC(); } - -void to_json(nlohmann::json& j, const NFCNormalizer& normalizer) { - j = { - {"type", "NFCNormalizer"}, - }; -} - -void from_json(const nlohmann::json& j, NFCNormalizer& normalizer) {} - -void NFKCNormalizer::operator()(NormalizedString* input) const { - input->NFKC(); -} - -void to_json(nlohmann::json& j, const NFKCNormalizer& normalizer) { - j = { - {"type", "NFKCNormalizer"}, - }; -} - -void from_json(const nlohmann::json& j, NFKCNormalizer& normalizer) {} - -void NFDNormalizer::operator()(NormalizedString* input) const { input->NFD(); } - -void to_json(nlohmann::json& j, const NFDNormalizer& normalizer) { - j = { - {"type", "NFDNormalizer"}, - }; -} - -void from_json(const nlohmann::json& j, NFDNormalizer& normalizer) {} - -void NFKDNormalizer::operator()(NormalizedString* input) const { - input->NFKD(); -} - -void to_json(nlohmann::json& j, const NFKDNormalizer& normalizer) { - j = { - {"type", "NFKDNormalizer"}, - }; -} - -void from_json(const nlohmann::json& j, NFKDNormalizer& normalizer) {} - -void NmtNormalizer::operator()(NormalizedString* input) const { - input->FilterChar([](char32_t ch) -> bool { - if ((ch >= 0x0001 && ch <= 0x0008) || (ch == 0x000B) || - (ch >= 0x000E && ch <= 0x001F) || (ch == 0x007F) || (ch == 0x008F) || - (ch == 0x009F)) { - return false; - } - return true; - }); - input->MapChar([](char32_t ch) -> char32_t { - if ((ch == 0x0009) || (ch == 0x000A) || (ch == 0x000C) || (ch == 0x000D) || - (ch == 0x1680) || (ch >= 0x200B && ch <= 0x200F) || (ch == 0x2028) || - (ch == 0x2029) || (ch == 0x2581) || (ch == 0xFEFF) || (ch == 0xFFFD)) { - return ' '; - } - return ch; - }); -} - -void to_json(nlohmann::json& j, const NmtNormalizer& normalizer) { - j = { - {"type", "NmtNormalizer"}, - }; -} - -void from_json(const nlohmann::json& j, NmtNormalizer& normalizer) {} - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/unicode.h b/fast_tokenizer/fast_tokenizer/normalizers/unicode.h deleted file mode 100644 index 6bf9c4b8de42..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/unicode.h +++ /dev/null @@ -1,57 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -struct FASTTOKENIZER_DECL NFCNormalizer : public Normalizer { - virtual void operator()(NormalizedString* input) const override; - friend void to_json(nlohmann::json& j, const NFCNormalizer& normalizer); - friend void from_json(const nlohmann::json& j, NFCNormalizer& normalizer); -}; - -struct FASTTOKENIZER_DECL NFDNormalizer : public Normalizer { - virtual void operator()(NormalizedString* input) const override; - friend void to_json(nlohmann::json& j, const NFDNormalizer& normalizer); - friend void from_json(const nlohmann::json& j, NFDNormalizer& normalizer); -}; - -struct FASTTOKENIZER_DECL NFKCNormalizer : public Normalizer { - virtual void operator()(NormalizedString* input) const override; - friend void to_json(nlohmann::json& j, const NFKCNormalizer& normalizer); - friend void from_json(const nlohmann::json& j, NFKCNormalizer& normalizer); -}; - -struct FASTTOKENIZER_DECL NFKDNormalizer : public Normalizer { - virtual void operator()(NormalizedString* input) const override; - friend void to_json(nlohmann::json& j, const NFKDNormalizer& normalizer); - friend void from_json(const nlohmann::json& j, NFKDNormalizer& normalizer); -}; - -struct FASTTOKENIZER_DECL NmtNormalizer : public Normalizer { - virtual void operator()(NormalizedString* input) const override; - friend void to_json(nlohmann::json& j, const NmtNormalizer& normalizer); - friend void from_json(const nlohmann::json& j, NmtNormalizer& normalizer); -}; - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/utils.cc b/fast_tokenizer/fast_tokenizer/normalizers/utils.cc deleted file mode 100644 index 15f6875b8749..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/utils.cc +++ /dev/null @@ -1,158 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/normalizers/utils.h" -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/normalizers/precompiled.h" -#include "fast_tokenizer/normalizers/replace.h" -#include "fast_tokenizer/normalizers/strip.h" -#include "fast_tokenizer/normalizers/unicode.h" -#include "unicode/unistr.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -void SequenceNormalizer::AppendNormalizer(Normalizer* normalizer) { - std::shared_ptr normalizer_ptr; - if (typeid(*normalizer) == typeid(SequenceNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(LowercaseNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(StripNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(StripAccentsNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(NFCNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(NFDNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(NFKCNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(NFKDNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(NmtNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(ReplaceNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(BertNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } else if (typeid(*normalizer) == typeid(PrecompiledNormalizer)) { - auto cast_normalizer = dynamic_cast(normalizer); - normalizer_ptr = std::make_shared(*cast_normalizer); - } - normalizer_ptrs_.push_back(std::move(normalizer_ptr)); -} - -SequenceNormalizer::SequenceNormalizer( - const std::vector& normalizers) { - for (auto& normalizer : normalizers) { - AppendNormalizer(normalizer); - } -} - -void SequenceNormalizer::operator()(NormalizedString* input) const { - std::string result; - for (auto& normalizer : normalizer_ptrs_) { - normalizer->operator()(input); - } -} -void LowercaseNormalizer::operator()(NormalizedString* input) const { - input->Lowercase(); -} - -void to_json(nlohmann::json& j, const SequenceNormalizer& normalizer) { - nlohmann::json jlist; - for (auto& ptr : normalizer.normalizer_ptrs_) { - nlohmann::json jitem; - if (typeid(*ptr) == typeid(SequenceNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(LowercaseNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(StripNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(StripAccentsNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(NFCNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(NFDNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(NFKCNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(NFKDNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(NmtNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(ReplaceNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(BertNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(PrecompiledNormalizer)) { - jitem = *dynamic_cast(ptr.get()); - } - jlist.push_back(jitem); - } - j = {{"type", "SequenceNormalizer"}, {"normalizers", jlist}}; -} - -void from_json(const nlohmann::json& j, - SequenceNormalizer& sequence_normalizer) { -#define TRY_APPEND_NORMALIZER(NORMALIZER_TYPE) \ - if (normalizer_type == #NORMALIZER_TYPE) { \ - NORMALIZER_TYPE normalizer; \ - normalizer_json.get_to(normalizer); \ - sequence_normalizer.AppendNormalizer(&normalizer); \ - } - - for (auto& normalizer_json : j.at("normalizers")) { - std::string normalizer_type; - normalizer_json.at("type").get_to(normalizer_type); - TRY_APPEND_NORMALIZER(BertNormalizer); - TRY_APPEND_NORMALIZER(PrecompiledNormalizer); - TRY_APPEND_NORMALIZER(ReplaceNormalizer); - TRY_APPEND_NORMALIZER(StripAccentsNormalizer); - TRY_APPEND_NORMALIZER(StripNormalizer); - TRY_APPEND_NORMALIZER(NFCNormalizer); - TRY_APPEND_NORMALIZER(NFKCNormalizer); - TRY_APPEND_NORMALIZER(NFDNormalizer); - TRY_APPEND_NORMALIZER(NFKDNormalizer); - TRY_APPEND_NORMALIZER(NmtNormalizer); - TRY_APPEND_NORMALIZER(LowercaseNormalizer); - TRY_APPEND_NORMALIZER(SequenceNormalizer); - } -#undef TRY_APPEND_NORMALIZER -} - -void to_json(nlohmann::json& j, const LowercaseNormalizer& normalizer) { - j = { - {"type", "LowercaseNormalizer"}, - }; -} - -void from_json(const nlohmann::json& j, LowercaseNormalizer& normalizer) {} - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/normalizers/utils.h b/fast_tokenizer/fast_tokenizer/normalizers/utils.h deleted file mode 100644 index 94fd2c91cdf0..000000000000 --- a/fast_tokenizer/fast_tokenizer/normalizers/utils.h +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace normalizers { - -struct FASTTOKENIZER_DECL SequenceNormalizer : public Normalizer { - SequenceNormalizer() = default; - SequenceNormalizer(const SequenceNormalizer&) = default; - SequenceNormalizer(const std::vector& normalizers); - virtual void operator()(NormalizedString* input) const override; - void AppendNormalizer(Normalizer* normalizer); - -private: - std::vector> normalizer_ptrs_; - friend void to_json(nlohmann::json& j, const SequenceNormalizer& normalizer); - friend void from_json(const nlohmann::json& j, - SequenceNormalizer& normalizer); -}; - -struct FASTTOKENIZER_DECL LowercaseNormalizer : public Normalizer { - virtual void operator()(NormalizedString* input) const override; - friend void to_json(nlohmann::json& j, const LowercaseNormalizer& normalizer); - friend void from_json(const nlohmann::json& j, - LowercaseNormalizer& normalizer); -}; - -} // namespace normalizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/postprocessors/CMakeLists.txt deleted file mode 100644 index 9d4aad766e77..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -cc_library(postprocessors SRCS bert.cc postprocessor.cc template.cc roberta.cc byte_level.cc DEPS core json) diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/bert.cc b/fast_tokenizer/fast_tokenizer/postprocessors/bert.cc deleted file mode 100644 index d40067c9d837..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/bert.cc +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "fast_tokenizer/core/encoding.h" -#include "glog/logging.h" -#include "fast_tokenizer/postprocessors/bert.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -BertPostProcessor::BertPostProcessor() - : sep_({"[SEP]", 102}), cls_({"[CLS]", 101}) {} -BertPostProcessor::BertPostProcessor( - const std::pair& sep, - const std::pair& cls) - : sep_(sep), cls_(cls) {} -size_t BertPostProcessor::AddedTokensNum(bool is_pair) const { - if (is_pair) { - // [CLS] A [SEP] B [SEP] - return 3; - } - // [CLS] A [SEP] - return 2; -} - -void BertPostProcessor::operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const { - if (!add_special_tokens) { - DefaultProcess(encoding, pair_encoding, result_encoding); - return; - } -// Construct the sequence as: [CLS] A [SEP] -#define CREATE_PROCESSED_ENCODING_SEQ( \ - encoding_ptr, attr, name, head_value, back_value) \ - auto encoding_##name = encoding_ptr->Get##attr(); \ - decltype(encoding_##name) name(encoding_##name.size() + 2); \ - std::copy(encoding_##name.begin(), encoding_##name.end(), name.begin() + 1); \ - name.front() = head_value; \ - name.back() = back_value - // ids - CREATE_PROCESSED_ENCODING_SEQ(encoding, Ids, ids, cls_.second, sep_.second); - // type_ids - CREATE_PROCESSED_ENCODING_SEQ(encoding, TypeIds, type_ids, 0, 0); - // tokens - CREATE_PROCESSED_ENCODING_SEQ( - encoding, Tokens, tokens, cls_.first, sep_.first); - // word_idx - CREATE_PROCESSED_ENCODING_SEQ(encoding, WordsIdx, word_idx, -1, -1); - // offsets - core::Offset empty_offsets = {0, 0}; - CREATE_PROCESSED_ENCODING_SEQ( - encoding, Offsets, offsets, empty_offsets, empty_offsets); - // special_tokens_mask - std::vector special_tokens_mask(ids.size(), 0); - special_tokens_mask.front() = special_tokens_mask.back() = 1; - // attention_mask - std::vector attention_mask(ids.size(), 1); - // sequence_ranges - std::unordered_map sequence_ranges; - sequence_ranges[0] = {1, ids.size() - 1}; - // overflowing - auto& overflowings = encoding->GetMutableOverflowing(); - for (auto& overflow_encoding : overflowings) { - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), Ids, ids, cls_.second, sep_.second); - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), TypeIds, type_ids, 0, 0); - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), Tokens, tokens, cls_.first, sep_.first); - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), WordsIdx, word_idx, -1, -1); - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), Offsets, offsets, empty_offsets, empty_offsets); - - std::vector special_tokens_mask(ids.size(), 0); - special_tokens_mask.front() = special_tokens_mask.back() = 1; - - std::vector attention_mask(ids.size(), 1); - - std::unordered_map sequence_ranges; - sequence_ranges[0] = {1, ids.size() - 1}; - - overflow_encoding = std::move( - core::Encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(word_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::vector(), // No overflowing - std::move(sequence_ranges))); - } - - core::Encoding new_encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(word_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::move(overflowings), - std::move(sequence_ranges)); - if (pair_encoding != nullptr) { -#define CREATE_PROCESSED_PARI_ENCODING_SEQ( \ - encoding_ptr, attr, name, back_value) \ - auto encoding_##name = encoding_ptr->Get##attr(); \ - decltype(encoding_##name) name(encoding_##name.size() + 1); \ - std::copy(encoding_##name.begin(), encoding_##name.end(), name.begin()); \ - name.back() = back_value - - CREATE_PROCESSED_PARI_ENCODING_SEQ(pair_encoding, Ids, ids, sep_.second); - CREATE_PROCESSED_PARI_ENCODING_SEQ(pair_encoding, TypeIds, type_ids, 1); - CREATE_PROCESSED_PARI_ENCODING_SEQ( - pair_encoding, Tokens, tokens, sep_.first); - CREATE_PROCESSED_PARI_ENCODING_SEQ(pair_encoding, WordsIdx, word_idx, -1); - core::Offset empty_offsets = {0, 0}; - CREATE_PROCESSED_PARI_ENCODING_SEQ( - pair_encoding, Offsets, offsets, empty_offsets); - - std::vector special_tokens_mask(ids.size(), 0); - special_tokens_mask.back() = 1; - - std::vector attention_mask(ids.size(), 1); - std::unordered_map sequence_ranges; - sequence_ranges[1] = {0, ids.size() - 1}; - // overflowing - auto& overflowings = pair_encoding->GetMutableOverflowing(); - for (auto& overflow_pair_encoding : overflowings) { - CREATE_PROCESSED_PARI_ENCODING_SEQ( - (&overflow_pair_encoding), Ids, ids, sep_.second); - CREATE_PROCESSED_PARI_ENCODING_SEQ( - (&overflow_pair_encoding), TypeIds, type_ids, 1); - CREATE_PROCESSED_PARI_ENCODING_SEQ( - (&overflow_pair_encoding), Tokens, tokens, sep_.first); - CREATE_PROCESSED_PARI_ENCODING_SEQ( - (&overflow_pair_encoding), WordsIdx, word_idx, -1); - core::Offset empty_offsets = {0, 0}; - CREATE_PROCESSED_PARI_ENCODING_SEQ( - (&overflow_pair_encoding), Offsets, offsets, empty_offsets); - - std::vector special_tokens_mask(ids.size(), 0); - special_tokens_mask.back() = 1; - - std::vector attention_mask(ids.size(), 1); - std::unordered_map sequence_ranges; - sequence_ranges[0] = {1, ids.size() - 1}; - - overflow_pair_encoding = std::move( - core::Encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(word_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::vector(), // No overflowing - std::move(sequence_ranges))); - } - - core::Encoding new_pair_encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(word_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::move(overflowings), - std::move(sequence_ranges)); - new_encoding.MergeWith(new_pair_encoding, false); - } -#undef CREATE_PROCESSED_ENCODING_SEQ -#undef CREATE_PROCESSED_PARI_ENCODING_SEQ - *result_encoding = std::move(new_encoding); -} - -void to_json(nlohmann::json& j, const BertPostProcessor& bert_postprocessor) { - j = { - {"type", "BertPostProcessor"}, - {"sep", bert_postprocessor.sep_}, - {"cls", bert_postprocessor.cls_}, - }; -} - -void from_json(const nlohmann::json& j, BertPostProcessor& bert_postprocessor) { - j["cls"].get_to(bert_postprocessor.cls_); - j["sep"].get_to(bert_postprocessor.sep_); -} - -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/bert.h b/fast_tokenizer/fast_tokenizer/postprocessors/bert.h deleted file mode 100644 index cc8c77f9785b..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/bert.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include "fast_tokenizer/postprocessors/postprocessor.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -struct FASTTOKENIZER_DECL BertPostProcessor : public PostProcessor { - BertPostProcessor(const std::pair& sep, - const std::pair& cls); - BertPostProcessor(); - virtual size_t AddedTokensNum(bool is_pair) const override; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override; - std::pair sep_; - std::pair cls_; - friend void to_json(nlohmann::json& j, - const BertPostProcessor& bert_postprocessor); - friend void from_json(const nlohmann::json& j, - BertPostProcessor& bert_postprocessor); -}; -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/byte_level.cc b/fast_tokenizer/fast_tokenizer/postprocessors/byte_level.cc deleted file mode 100644 index 51f0b45ecec4..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/byte_level.cc +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "fast_tokenizer/postprocessors/byte_level.h" -#include "fast_tokenizer/pretokenizers/byte_level.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -ByteLevelPostProcessor::ByteLevelPostProcessor(bool add_prefix_space, - bool trim_offsets, - bool use_regex) - : add_prefix_space_(add_prefix_space), - trim_offsets_(trim_offsets), - use_regex_(use_regex) {} - - -size_t ByteLevelPostProcessor::AddedTokensNum(bool is_pair) const { return 0; } - -void ByteLevelPostProcessor::operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const { - if (trim_offsets_) { - pretokenizers::ProcessOffsets(encoding, add_special_tokens); - for (auto& overflowing : encoding->GetMutableOverflowing()) { - pretokenizers::ProcessOffsets(&overflowing, add_special_tokens); - } - if (pair_encoding != nullptr) { - pretokenizers::ProcessOffsets(pair_encoding, add_special_tokens); - for (auto& overflowing : pair_encoding->GetMutableOverflowing()) { - pretokenizers::ProcessOffsets(&overflowing, add_special_tokens); - } - } - } - - encoding->SetSequenceIds(0); - if (pair_encoding != nullptr) { - pair_encoding->SetSequenceIds(1); - } -} - -void to_json(nlohmann::json& j, - const ByteLevelPostProcessor& byte_level_postprocessor) { - j = { - {"type", "ByteLevelPostProcessor"}, - {"add_prefix_space", byte_level_postprocessor.add_prefix_space_}, - {"trim_offsets", byte_level_postprocessor.trim_offsets_}, - {"use_regex", byte_level_postprocessor.use_regex_}, - }; -} - -void from_json(const nlohmann::json& j, - ByteLevelPostProcessor& byte_level_postprocessor) { - j["add_prefix_space"].get_to(byte_level_postprocessor.add_prefix_space_); - j["trim_offsets"].get_to(byte_level_postprocessor.trim_offsets_); - j["use_regex"].get_to(byte_level_postprocessor.use_regex_); -} - -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/byte_level.h b/fast_tokenizer/fast_tokenizer/postprocessors/byte_level.h deleted file mode 100644 index 75cdd995fec5..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/byte_level.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include "fast_tokenizer/postprocessors/postprocessor.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -struct FASTTOKENIZER_DECL ByteLevelPostProcessor : public PostProcessor { - ByteLevelPostProcessor(bool add_prefix_space = true, - bool trim_offsets = true, - bool use_regex = true); - virtual size_t AddedTokensNum(bool is_pair) const override; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override; - - friend void to_json(nlohmann::json& j, - const ByteLevelPostProcessor& byte_level_postprocessor); - friend void from_json(const nlohmann::json& j, - ByteLevelPostProcessor& byte_level_postprocessor); - bool add_prefix_space_; - bool trim_offsets_; - bool use_regex_; -}; - -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/postprocessor.cc b/fast_tokenizer/fast_tokenizer/postprocessors/postprocessor.cc deleted file mode 100644 index abe994beb7d8..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/postprocessor.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/postprocessors/postprocessor.h" -#include "fast_tokenizer/core/encoding.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -void PostProcessor::DefaultProcess(core::Encoding* encoding, - core::Encoding* pair_encoding, - core::Encoding* result_encoding) { - if (pair_encoding == nullptr) { - *result_encoding = *encoding; - } else { - encoding->SetSequenceIds(0); - pair_encoding->SetSequenceIds(1); - encoding->MergeWith(*pair_encoding, false); - *result_encoding = *encoding; - } -} - -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/postprocessor.h b/fast_tokenizer/fast_tokenizer/postprocessors/postprocessor.h deleted file mode 100644 index 34fda8377a2a..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/postprocessor.h +++ /dev/null @@ -1,41 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { - -namespace core { -class Encoding; -} // namespace core - -namespace postprocessors { - -struct FASTTOKENIZER_DECL PostProcessor { - virtual size_t AddedTokensNum(bool is_pair) const = 0; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const = 0; - static void DefaultProcess(core::Encoding* encoding, - core::Encoding* pair_encoding, - core::Encoding* result_encoding); -}; -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/postprocessors.h b/fast_tokenizer/fast_tokenizer/postprocessors/postprocessors.h deleted file mode 100644 index 9427f2478b9a..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/postprocessors.h +++ /dev/null @@ -1,21 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/postprocessors/bert.h" -#include "fast_tokenizer/postprocessors/byte_level.h" -#include "fast_tokenizer/postprocessors/postprocessor.h" -#include "fast_tokenizer/postprocessors/roberta.h" -#include "fast_tokenizer/postprocessors/template.h" diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/roberta.cc b/fast_tokenizer/fast_tokenizer/postprocessors/roberta.cc deleted file mode 100644 index 4a468847c222..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/roberta.cc +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/postprocessors/roberta.h" -#include "fast_tokenizer/pretokenizers/byte_level.h" -#include "glog/logging.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -RobertaPostProcessor::RobertaPostProcessor( - const std::pair& sep, - const std::pair& cls, - bool trim_offsets, - bool add_prefix_space) - : sep_(sep), - cls_(cls), - trim_offsets_(trim_offsets), - add_prefix_space_(add_prefix_space) {} - -size_t RobertaPostProcessor::AddedTokensNum(bool is_pair) const { - if (is_pair) { - return 4; - } - return 2; -} - -void RobertaPostProcessor::operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const { - if (trim_offsets_) { - pretokenizers::ProcessOffsets(encoding, add_special_tokens); - for (auto& overflowing : encoding->GetMutableOverflowing()) { - pretokenizers::ProcessOffsets(&overflowing, add_special_tokens); - } - if (pair_encoding != nullptr) { - pretokenizers::ProcessOffsets(pair_encoding, add_special_tokens); - for (auto& overflowing : pair_encoding->GetMutableOverflowing()) { - pretokenizers::ProcessOffsets(&overflowing, add_special_tokens); - } - } - } - encoding->SetTypeIds(std::vector(encoding->GetLen(), 0)); - if (pair_encoding != nullptr) { - pair_encoding->SetTypeIds( - std::vector(pair_encoding->GetLen(), 0)); - } - if (!add_special_tokens) { - DefaultProcess(encoding, pair_encoding, result_encoding); - return; - } -// Construct the sequence as: [CLS] A [SEP] -#define CREATE_PROCESSED_ENCODING_SEQ( \ - encoding_ptr, attr, name, head_value, back_value) \ - auto encoding_##name = encoding_ptr->Get##attr(); \ - decltype(encoding_##name) name(encoding_##name.size() + 2); \ - std::copy(encoding_##name.begin(), encoding_##name.end(), name.begin() + 1); \ - name.front() = head_value; \ - name.back() = back_value - // ids - CREATE_PROCESSED_ENCODING_SEQ(encoding, Ids, ids, cls_.second, sep_.second); - // type_ids. Because there is no nsp task in the roberta, all type_ids are set - // to 0. - std::vector type_ids(encoding->GetTypeIds().size() + 2, 0); - // tokens - CREATE_PROCESSED_ENCODING_SEQ( - encoding, Tokens, tokens, cls_.first, sep_.first); - // word_idx - CREATE_PROCESSED_ENCODING_SEQ(encoding, WordsIdx, word_idx, -1, -1); - // offsets - core::Offset empty_offsets = {0, 0}; - CREATE_PROCESSED_ENCODING_SEQ( - encoding, Offsets, offsets, empty_offsets, empty_offsets); - // special_tokens_mask - std::vector special_tokens_mask(ids.size(), 0); - special_tokens_mask.front() = special_tokens_mask.back() = 1; - // attention_mask - std::vector attention_mask(ids.size(), 1); - // sequence_ranges - std::unordered_map sequence_ranges; - sequence_ranges[0] = {1, ids.size() - 1}; - // overflowing - auto& overflowings = encoding->GetMutableOverflowing(); - for (auto& overflow_encoding : overflowings) { - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), Ids, ids, cls_.second, sep_.second); - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), TypeIds, type_ids, 0, 0); - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), Tokens, tokens, cls_.first, sep_.first); - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), WordsIdx, word_idx, -1, -1); - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_encoding), Offsets, offsets, empty_offsets, empty_offsets); - - std::vector special_tokens_mask(ids.size(), 0); - special_tokens_mask.front() = special_tokens_mask.back() = 1; - - std::vector attention_mask(ids.size(), 1); - - std::unordered_map sequence_ranges; - sequence_ranges[0] = {1, ids.size() - 1}; - - overflow_encoding = std::move( - core::Encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(word_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::vector(), // No overflowing - std::move(sequence_ranges))); - } - - core::Encoding new_encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(word_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::move(overflowings), - std::move(sequence_ranges)); - - // Construct the sequence as: [CLS] A [SEP] [SEP] B [SEP] - if (pair_encoding != nullptr) { - // ids - CREATE_PROCESSED_ENCODING_SEQ( - pair_encoding, Ids, ids, sep_.second, sep_.second); - // type_ids. Because there is no nsp task in the roberta, all type_ids are - // set to 0. - std::vector type_ids(pair_encoding->GetTypeIds().size() + 2, 0); - // tokens - CREATE_PROCESSED_ENCODING_SEQ( - pair_encoding, Tokens, tokens, sep_.first, sep_.first); - // word_idx - CREATE_PROCESSED_ENCODING_SEQ(pair_encoding, WordsIdx, word_idx, -1, -1); - // offsets - core::Offset empty_offsets = {0, 0}; - CREATE_PROCESSED_ENCODING_SEQ( - pair_encoding, Offsets, offsets, empty_offsets, empty_offsets); - // special_tokens_mask - std::vector special_tokens_mask(ids.size(), 0); - special_tokens_mask.front() = special_tokens_mask.back() = 1; - // attention_mask - std::vector attention_mask(ids.size(), 1); - // sequence_ranges - std::unordered_map sequence_ranges; - sequence_ranges[1] = {1, ids.size() - 1}; - // overflowing - auto& overflowings = pair_encoding->GetMutableOverflowing(); - for (auto& overflow_pair_encoding : overflowings) { - // ids - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_pair_encoding), Ids, ids, sep_.second, sep_.second); - // type_ids - std::vector type_ids( - overflow_pair_encoding.GetTypeIds().size() + 2, 0); - // tokens - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_pair_encoding), Tokens, tokens, sep_.first, sep_.first); - // word_idx - CREATE_PROCESSED_ENCODING_SEQ( - (&overflow_pair_encoding), WordsIdx, word_idx, -1, -1); - // offsets - core::Offset empty_offsets = {0, 0}; - CREATE_PROCESSED_ENCODING_SEQ((&overflow_pair_encoding), - Offsets, - offsets, - empty_offsets, - empty_offsets); - // special_tokens_mask - std::vector special_tokens_mask(ids.size(), 0); - special_tokens_mask.front() = special_tokens_mask.back() = 1; - // attention_mask - std::vector attention_mask(ids.size(), 1); - // sequence_ranges - std::unordered_map sequence_ranges; - sequence_ranges[0] = {1, ids.size() - 1}; - overflow_pair_encoding = std::move( - core::Encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(word_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::vector(), // No overflowing - std::move(sequence_ranges))); - } - core::Encoding new_pair_encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(word_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::move(overflowings), - std::move(sequence_ranges)); - new_encoding.MergeWith(new_pair_encoding, false); - } -#undef CREATE_PROCESSED_ENCODING_SEQ - *result_encoding = std::move(new_encoding); -} - -void to_json(nlohmann::json& j, - const RobertaPostProcessor& roberta_postprocessor) { - j = { - {"type", "RobertaPostProcessor"}, - {"sep", roberta_postprocessor.sep_}, - {"cls", roberta_postprocessor.cls_}, - {"trim_offsets", roberta_postprocessor.trim_offsets_}, - {"add_prefix_space", roberta_postprocessor.add_prefix_space_}, - }; -} - -void from_json(const nlohmann::json& j, - RobertaPostProcessor& roberta_postprocessor) { - j["cls"].get_to(roberta_postprocessor.cls_); - j["sep"].get_to(roberta_postprocessor.sep_); - j["trim_offsets"].get_to(roberta_postprocessor.trim_offsets_); - j["add_prefix_space"].get_to(roberta_postprocessor.add_prefix_space_); -} - -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/roberta.h b/fast_tokenizer/fast_tokenizer/postprocessors/roberta.h deleted file mode 100644 index 0601882f1df1..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/roberta.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include "fast_tokenizer/postprocessors/postprocessor.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -struct FASTTOKENIZER_DECL RobertaPostProcessor : public PostProcessor { - RobertaPostProcessor(const std::pair& sep = {"", - 2}, - const std::pair& cls = {"", 0}, - bool trim_offsets = true, - bool add_prefix_space = true); - virtual size_t AddedTokensNum(bool is_pair) const override; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override; - std::pair sep_; - std::pair cls_; - bool trim_offsets_; - bool add_prefix_space_; - friend void to_json(nlohmann::json& j, - const RobertaPostProcessor& roberta_postprocessor); - friend void from_json(const nlohmann::json& j, - RobertaPostProcessor& roberta_postprocessor); -}; -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/template.cc b/fast_tokenizer/fast_tokenizer/postprocessors/template.cc deleted file mode 100644 index 28a3eb92587e..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/template.cc +++ /dev/null @@ -1,454 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include - -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/postprocessors/template.h" -#include "glog/logging.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -void ParseIdFromString(const std::string& template_id_string, - TemplatePiece* template_piece) { - if (template_id_string.find_first_of("$") == 0) { - *template_piece = TemplateSequence(); - auto& seq = paddlenlp::get(*template_piece); - std::string rest = - template_id_string.substr(template_id_string.find_first_not_of("$")); - if (rest == "" || rest == "A" || rest == "a") { - seq = TemplateSequence{SequenceType::SEQ_A, 0}; - } else if (rest == "B" || rest == "b") { - seq = TemplateSequence{SequenceType::SEQ_B, 0}; - } else { - std::string::size_type sz; - uint32_t type_id = std::stoul(rest, &sz); - if (sz = rest.length()) { - seq = TemplateSequence{SequenceType::SEQ_A, type_id}; - } else { - throw std::runtime_error( - "ParseIdFromString error! The format of template piece id should " - "be " - "$A, $a, $B, $b or ${type_id}"); - } - } - } else { - *template_piece = TemplateSpecialToken(); - paddlenlp::get(*template_piece) = {template_id_string, - 0}; - } -} - -void SetTypeId(uint32_t type_id, TemplatePiece* template_piece) { - if (paddlenlp::get_if(template_piece) != nullptr) { - paddlenlp::get(*template_piece).second = type_id; - } else { - paddlenlp::get(*template_piece).second = type_id; - } -} - -void GetTemplatePieceFromString(const std::string& template_string, - TemplatePiece* template_piece) { - auto spliter_idx = template_string.find_first_of(":"); - if (spliter_idx == std::string::npos) { - ParseIdFromString(template_string, template_piece); - } else { - std::string template_id_string = template_string.substr(0, spliter_idx); - std::string template_type_id_string = - template_string.substr(spliter_idx + 1); - ParseIdFromString(template_id_string, template_piece); - - std::string::size_type sz; - uint32_t type_id = std::stoul(template_type_id_string, &sz); - if (sz == template_type_id_string.length()) { - SetTypeId(type_id, template_piece); - } else { - throw std::runtime_error( - "ParseTypeIdFromString error! The type id should be unsigned " - "integer."); - } - } -} - -void to_json(nlohmann::json& j, const TemplatePiece& template_piece) { - if (paddlenlp::get_if(&template_piece) != nullptr) { - auto& template_sequence = paddlenlp::get(template_piece); - j = { - {"Sequence", - { - {"id", template_sequence.first}, - {"type_id", template_sequence.second}, - }}, - }; - } else { - auto& template_special_token = - paddlenlp::get(template_piece); - j = { - {"SpecialToken", - { - {"id", template_special_token.first}, - {"type_id", template_special_token.second}, - }}, - }; - } -} - -void from_json(const nlohmann::json& j, TemplatePiece& template_piece) { - if (j.find("Sequence") != j.end()) { - template_piece = - TemplateSequence(j["Sequence"]["id"], j["Sequence"]["type_id"]); - } else { - template_piece = TemplateSpecialToken(j["SpecialToken"]["id"], - j["SpecialToken"]["type_id"]); - } -} - -void to_json(nlohmann::json& j, const SpecialToken& special_token) { - j = { - {"id", special_token.id_}, - {"ids", special_token.ids_}, - {"tokens", special_token.tokens_}, - }; -} - -void from_json(const nlohmann::json& j, SpecialToken& special_token) { - j["id"].get_to(special_token.id_); - j["ids"].get_to(special_token.ids_); - j["tokens"].get_to(special_token.tokens_); -} - -size_t TemplatePostProcessor::CountAdded( - Template* template_, const SpecialTokensMap& special_tokens_map) { - size_t count = 0; - for (auto& piece : template_->pieces_) { - TemplateSpecialToken* special_token = - paddlenlp::get_if(&piece); - if (special_token != nullptr) { - auto token_iter = - special_tokens_map.tokens_map_.find(special_token->first); - if (token_iter != special_tokens_map.tokens_map_.end()) { - count += token_iter->second.ids_.size(); - } - } - } - return count; -} - -void to_json(nlohmann::json& j, const Template& template_) { - for (auto& piece : template_.pieces_) { - j.push_back(piece); - } -} - -void from_json(const nlohmann::json& j, Template& template_) { - template_.pieces_.resize(j.size()); - for (int i = 0; i < j.size(); ++i) { - j[i].get_to(template_.pieces_[i]); - } -} - -void to_json(nlohmann::json& j, const SpecialTokensMap& tokens_map) { - for (auto it = tokens_map.tokens_map_.begin(); - it != tokens_map.tokens_map_.end(); - ++it) { - j[it->first] = it->second; - } -} - -void from_json(const nlohmann::json& j, SpecialTokensMap& tokens_map) { - SpecialToken special_token; - for (auto it = j.begin(); it != j.end(); ++it) { - tokens_map.tokens_map_[it.key()] = it.value().get_to(special_token); - } -} - -size_t TemplatePostProcessor::DefaultAdded(bool is_single) { - Template* target = nullptr; - if (is_single) { - target = &single_; - } else { - target = &pair_; - } - return CountAdded(target, special_tokens_map_); -} - -void TemplatePostProcessor::UpdateAddedTokensNum() { - added_single_ = DefaultAdded(true); - added_pair_ = DefaultAdded(false); -} - -void TemplatePostProcessor::UpdateSinglePieces( - const std::string& template_str) { - single_.GetPiecesFromStr(template_str); - added_single_ = DefaultAdded(true); -} - -void TemplatePostProcessor::UpdateSinglePieces( - const std::vector& pieces) { - single_.GetPiecesFromVec(pieces); - added_single_ = DefaultAdded(true); -} - -void TemplatePostProcessor::UpdatePairPieces(const std::string& template_str) { - pair_.GetPiecesFromStr(template_str); - added_pair_ = DefaultAdded(false); -} - -void TemplatePostProcessor::UpdatePairPieces( - const std::vector& pieces) { - pair_.GetPiecesFromVec(pieces); - added_pair_ = DefaultAdded(false); -} - -TemplatePostProcessor::TemplatePostProcessor() { UpdateAddedTokensNum(); } - -TemplatePostProcessor::TemplatePostProcessor( - const Template& single, - const Template& pair, - const std::vector& special_tokens_map) - : single_(single), pair_(pair), special_tokens_map_(special_tokens_map) { - UpdateAddedTokensNum(); -} - -size_t TemplatePostProcessor::AddedTokensNum(bool is_pair) const { - if (is_pair) { - return added_pair_; - } - return added_single_; -} - -void TemplatePostProcessor::SetTokensMap( - const std::vector& special_tokens) { - special_tokens_map_.SetTokensMap(special_tokens); - UpdateAddedTokensNum(); -} - -void TemplatePostProcessor::ApplyTemplate( - const Template& pieces, - core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const { - size_t new_size = 0; - for (auto&& piece : pieces.pieces_) { - if (paddlenlp::get_if(&piece) != nullptr) { - auto seq_type = paddlenlp::get(piece).first; - if (seq_type == SequenceType::SEQ_A) { - new_size += encoding->GetLen(); - } else { - if (pair_encoding == nullptr) { - throw std::runtime_error( - "Template expected a pair sequence, but none provided"); - } - new_size += pair_encoding->GetLen(); - } - } else { - if (add_special_tokens) { - auto&& special_token = - paddlenlp::get(piece).first; - if (special_tokens_map_.tokens_map_.find(special_token) != - special_tokens_map_.tokens_map_.end()) { - new_size += - special_tokens_map_.tokens_map_.at(special_token).ids_.size(); - } - } - } - } - std::vector ids; - ids.reserve(new_size); - std::vector type_ids; - type_ids.reserve(new_size); - std::vector tokens; - tokens.reserve(new_size); - std::vector words_idx; - words_idx.reserve(new_size); - std::vector offsets; - offsets.reserve(new_size); - std::vector special_tokens_mask; - special_tokens_mask.reserve(new_size); - std::vector attention_mask; - attention_mask.reserve(new_size); - std::unordered_map sequence_ranges; - std::vector result_overflowings; - auto& overflowings = encoding->GetMutableOverflowing(); - - core::Encoding result_overflowing_encoding; - for (auto& overflow_encoding : overflowings) { - core::Encoding encoding_copy = overflow_encoding; - core::Encoding pair_encoding_copy; - if (pair_encoding != nullptr) { - pair_encoding_copy = *pair_encoding; - ApplyTemplate(pieces, - &encoding_copy, - &pair_encoding_copy, - add_special_tokens, - &result_overflowing_encoding); - result_overflowings.push_back(result_overflowing_encoding); - for (auto& pair_overflow_encoding : - pair_encoding->GetMutableOverflowing()) { - core::Encoding pair_encoding_copy = pair_overflow_encoding; - ApplyTemplate(pieces, - &encoding_copy, - &pair_encoding_copy, - add_special_tokens, - &result_overflowing_encoding); - result_overflowings.push_back(result_overflowing_encoding); - } - } else { - ApplyTemplate(pieces, - &encoding_copy, - pair_encoding, - add_special_tokens, - &result_overflowing_encoding); - result_overflowings.push_back(result_overflowing_encoding); - } - } - if (pair_encoding != nullptr) { - for (auto& pair_overflow_encoding : - pair_encoding->GetMutableOverflowing()) { - core::Encoding encoding_copy = *encoding; - core::Encoding pair_encoding_copy = pair_overflow_encoding; - ApplyTemplate(pieces, - &encoding_copy, - &pair_encoding_copy, - add_special_tokens, - &result_overflowing_encoding); - result_overflowings.push_back(result_overflowing_encoding); - } - } - VLOG(6) << "Template pieces num: " << pieces.pieces_.size(); - for (auto& piece : pieces.pieces_) { - if (paddlenlp::get_if(&piece) != nullptr) { - auto& template_sequence = paddlenlp::get(piece); - if (template_sequence.first == SequenceType::SEQ_A) { - auto seq_start = ids.size(); - auto seq_end = seq_start + encoding->GetLen(); - sequence_ranges[0] = {seq_start, seq_end}; - ids.insert( - ids.end(), encoding->GetIds().begin(), encoding->GetIds().end()); - type_ids.insert( - type_ids.end(), encoding->GetLen(), template_sequence.second); - tokens.insert(tokens.end(), - encoding->GetTokens().begin(), - encoding->GetTokens().end()); - words_idx.insert(words_idx.end(), - encoding->GetWordsIdx().begin(), - encoding->GetWordsIdx().end()); - offsets.insert(offsets.end(), - encoding->GetOffsets().begin(), - encoding->GetOffsets().end()); - special_tokens_mask.insert(special_tokens_mask.end(), - encoding->GetSpecialTokensMask().begin(), - encoding->GetSpecialTokensMask().end()); - attention_mask.insert(attention_mask.end(), - encoding->GetAttentionMask().begin(), - encoding->GetAttentionMask().end()); - } else if (template_sequence.first == SequenceType::SEQ_B) { - if (pair_encoding == nullptr) { - throw std::runtime_error("Missing pair sequence, checked above"); - } - auto seq_start = ids.size(); - auto seq_end = seq_start + pair_encoding->GetLen(); - sequence_ranges[0] = {seq_start, seq_end}; - ids.insert(ids.end(), - pair_encoding->GetIds().begin(), - pair_encoding->GetIds().end()); - type_ids.insert( - type_ids.end(), pair_encoding->GetLen(), template_sequence.second); - tokens.insert(tokens.end(), - pair_encoding->GetTokens().begin(), - pair_encoding->GetTokens().end()); - words_idx.insert(words_idx.end(), - pair_encoding->GetWordsIdx().begin(), - pair_encoding->GetWordsIdx().end()); - offsets.insert(offsets.end(), - pair_encoding->GetOffsets().begin(), - pair_encoding->GetOffsets().end()); - special_tokens_mask.insert( - special_tokens_mask.end(), - pair_encoding->GetSpecialTokensMask().begin(), - pair_encoding->GetSpecialTokensMask().end()); - attention_mask.insert(attention_mask.end(), - pair_encoding->GetAttentionMask().begin(), - pair_encoding->GetAttentionMask().end()); - } - } else { - auto& special_token = paddlenlp::get(piece); - if (add_special_tokens) { - const std::string& id = special_token.first; - uint32_t type_id = special_token.second; - auto& tok = special_tokens_map_.tokens_map_.at( - id); // We already checked existance above - auto size = tok.ids_.size(); - ids.insert(ids.end(), tok.ids_.begin(), tok.ids_.end()); - type_ids.insert(type_ids.end(), size, type_id); - tokens.insert(tokens.end(), tok.tokens_.begin(), tok.tokens_.end()); - words_idx.insert(words_idx.end(), size, -1 /* 2^32 */); - offsets.insert(offsets.end(), size, {0, 0}); - special_tokens_mask.insert(special_tokens_mask.end(), size, 1); - attention_mask.insert(attention_mask.end(), size, 1); - } - } - } - *result_encoding = core::Encoding(std::move(ids), - std::move(type_ids), - std::move(tokens), - std::move(words_idx), - std::move(offsets), - std::move(special_tokens_mask), - std::move(attention_mask), - std::move(result_overflowings), - std::move(sequence_ranges)); -} - -void TemplatePostProcessor::operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const { - if (pair_encoding != nullptr) { - ApplyTemplate( - pair_, encoding, pair_encoding, add_special_tokens, result_encoding); - } else { - ApplyTemplate( - single_, encoding, pair_encoding, add_special_tokens, result_encoding); - } -} - -void to_json(nlohmann::json& j, - const TemplatePostProcessor& template_postprocessor) { - j = { - {"type", "TemplateProcessing"}, - {"single", template_postprocessor.single_}, - {"pair", template_postprocessor.pair_}, - {"special_tokens", template_postprocessor.special_tokens_map_}, - }; -} - -void from_json(const nlohmann::json& j, - TemplatePostProcessor& template_postprocessor) { - j["single"].get_to(template_postprocessor.single_); - j["pair"].get_to(template_postprocessor.pair_); - j["special_tokens"].get_to(template_postprocessor.special_tokens_map_); - template_postprocessor.added_single_ = - template_postprocessor.DefaultAdded(true); - template_postprocessor.added_pair_ = - template_postprocessor.DefaultAdded(false); -} - -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/postprocessors/template.h b/fast_tokenizer/fast_tokenizer/postprocessors/template.h deleted file mode 100644 index aa20de483daa..000000000000 --- a/fast_tokenizer/fast_tokenizer/postprocessors/template.h +++ /dev/null @@ -1,191 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include - -#include "fast_tokenizer/postprocessors/postprocessor.h" -#include "fast_tokenizer/utils/utils.h" -#include "fast_tokenizer/utils/variant.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace postprocessors { - -enum FASTTOKENIZER_DECL SequenceType { SEQ_A, SEQ_B }; -NLOHMANN_JSON_SERIALIZE_ENUM(SequenceType, - { - {SEQ_A, "A"}, {SEQ_B, "B"}, - }); -// The template indicate `${Id} : ${TypeId}` -using TemplateSequence = std::pair; -using TemplateSpecialToken = std::pair; - -using TemplatePiece = - paddlenlp::variant; -void to_json(nlohmann::json& j, const TemplatePiece& template_piece); -void from_json(const nlohmann::json& j, TemplatePiece& template_piece); - -void ParseIdFromString(const std::string& template_id_string, - TemplatePiece* template_piece); -void SetTypeId(uint32_t type_id, TemplatePiece* template_piece); -void GetTemplatePieceFromString(const std::string& template_string, - TemplatePiece* template_piece); - -struct FASTTOKENIZER_DECL SpecialToken { - std::string id_; - std::vector ids_; - std::vector tokens_; - SpecialToken() = default; - SpecialToken(const std::string& id, - const std::vector& ids, - const std::vector& tokens) - : id_(id), ids_(ids), tokens_(tokens) {} - SpecialToken(const std::string& token, uint32_t id) { - id_ = token; - ids_.push_back(id); - tokens_.push_back(token); - } - friend void to_json(nlohmann::json& j, const SpecialToken& special_token); - friend void from_json(const nlohmann::json& j, SpecialToken& special_token); -}; - -struct FASTTOKENIZER_DECL Template { - std::vector pieces_; - Template() = default; - explicit Template(const std::string& template_str) { - std::vector pieces; - - // Parse the pieces - size_t start = template_str.find_first_not_of(" "); - size_t pos; - while ((pos = template_str.find_first_of(" ", start)) != - std::string::npos) { - pieces.push_back(template_str.substr(start, pos - start)); - start = template_str.find_first_not_of(" ", pos); - } - if (start != std::string::npos) { - pieces.push_back(template_str.substr(start)); - } - AddStringPiece(pieces); - } - - explicit Template(const std::vector& pieces) - : pieces_(pieces) {} - explicit Template(const std::vector& pieces) { - AddStringPiece(pieces); - } - - void GetPiecesFromVec(const std::vector& pieces) { - AddStringPiece(pieces); - } - - void GetPiecesFromStr(const std::string& template_str) { - std::vector pieces; - - // Parse the pieces - size_t start = template_str.find_first_not_of(" "); - size_t pos; - while ((pos = template_str.find_first_of(" ", start)) != - std::string::npos) { - pieces.push_back(template_str.substr(start, pos - start)); - start = template_str.find_first_not_of(" ", pos); - } - if (start != std::string::npos) { - pieces.push_back(template_str.substr(start)); - } - AddStringPiece(pieces); - } - - void Clean() { pieces_.clear(); } - -private: - void AddStringPiece(const std::vector& pieces) { - for (auto&& piece : pieces) { - TemplatePiece template_piece; - GetTemplatePieceFromString(piece, &template_piece); - if (paddlenlp::get_if(&template_piece)) { - pieces_.push_back(paddlenlp::get(template_piece)); - } else { - pieces_.push_back(paddlenlp::get(template_piece)); - } - } - } - - friend void to_json(nlohmann::json& j, const Template& template_); - friend void from_json(const nlohmann::json& j, Template& template_); -}; - -struct FASTTOKENIZER_DECL SpecialTokensMap { - std::unordered_map tokens_map_; - SpecialTokensMap() = default; - explicit SpecialTokensMap(const std::vector& special_tokens) { - SetTokensMap(special_tokens); - } - void SetTokensMap(const std::vector& special_tokens) { - tokens_map_.clear(); - for (const auto& special_token : special_tokens) { - tokens_map_.insert({special_token.id_, special_token}); - } - } - friend void to_json(nlohmann::json& j, const SpecialTokensMap& tokens_map); - friend void from_json(const nlohmann::json& j, SpecialTokensMap& tokens_map); -}; - -struct FASTTOKENIZER_DECL TemplatePostProcessor : public PostProcessor { - TemplatePostProcessor(); - TemplatePostProcessor(const Template&, - const Template&, - const std::vector&); - - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override; - virtual size_t AddedTokensNum(bool is_pair) const override; - - void UpdateSinglePieces(const std::string& template_str); - void UpdateSinglePieces(const std::vector& pieces); - void UpdatePairPieces(const std::string& template_str); - void UpdatePairPieces(const std::vector& pieces); - void UpdateAddedTokensNum(); - void SetTokensMap(const std::vector& special_tokens); - size_t CountAdded(Template* template_, - const SpecialTokensMap& special_tokens_map); - size_t DefaultAdded(bool is_single = true); - void ApplyTemplate(const Template& pieces, - core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const; - - friend void to_json(nlohmann::json& j, - const TemplatePostProcessor& template_postprocessor); - friend void from_json(const nlohmann::json& j, - TemplatePostProcessor& template_postprocessor); - - Template single_; - Template pair_; - size_t added_single_; - size_t added_pair_; - SpecialTokensMap special_tokens_map_; -}; - -} // namespace postprocessors -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/pretokenizers/CMakeLists.txt deleted file mode 100644 index 2acf3a9a7a5d..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -cc_library(pretokenizers - SRCS pretokenizer.cc whitespace.cc bert.cc metaspace.cc - sequence.cc byte_level.cc split.cc whitespace_and_punctuation.cc - DEPS normalizers core json utils) diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/bert.cc b/fast_tokenizer/fast_tokenizer/pretokenizers/bert.cc deleted file mode 100644 index ce45d0c316f2..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/bert.cc +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pretokenizers/bert.h" - -#include "fast_tokenizer/utils/utils.h" -#include "glog/logging.h" -#include "re2/re2.h" -#include "unicode/uchar.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -// Note (zhoushunjie): Init re2::RE2 objects cost too much, -// ensure not init in the operator() -static re2::RE2 pattern("[\\s\\p{Zs}]+"); -static re2::RE2 punc_pattern("[[:punct:]]|[\\pP]"); - -void BertPreTokenizer::operator()(PreTokenizedString* pretokenized) const { - std::vector normalized_splits; - pretokenized->Split([&normalized_splits]( - int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - // Use single character match instead of regex to improve performance - normalized->Split([](char32_t ch) -> bool { return u_isUWhiteSpace(ch); }, - core::SplitMode::REMOVED, - &normalized_splits); - for (auto&& normalize : normalized_splits) { - if (!normalize.IsEmpty()) { - string_splits->emplace_back(std::move(normalize)); - } - } - }); - normalized_splits.clear(); - pretokenized->Split([&normalized_splits]( - int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - // Use single character match instead of regex to improve performance - normalized->Split( - utils::IsPunctuation, core::SplitMode::ISOLATED, &normalized_splits); - for (auto&& normalize : normalized_splits) { - if (!normalize.IsEmpty()) { - VLOG(6) << "After pretokenized: " << normalize.GetStr(); - string_splits->emplace_back(std::move(normalize)); - } - } - }); -} - -void to_json(nlohmann::json& j, const BertPreTokenizer& bert_pre_tokenizer) { - j = { - {"type", "BertPreTokenizer"}, - }; -} - -void from_json(const nlohmann::json& j, BertPreTokenizer& bert_pre_tokenizer) {} - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/bert.h b/fast_tokenizer/fast_tokenizer/pretokenizers/bert.h deleted file mode 100644 index 7543ac5d25f2..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/bert.h +++ /dev/null @@ -1,35 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -struct FASTTOKENIZER_DECL BertPreTokenizer : public PreTokenizer { - virtual void operator()(PreTokenizedString* pretokenized) const override; - friend void to_json(nlohmann::json& j, - const BertPreTokenizer& bert_pre_tokenizer); - friend void from_json(const nlohmann::json& j, - BertPreTokenizer& bert_pre_tokenizer); -}; - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/byte_level.cc b/fast_tokenizer/fast_tokenizer/pretokenizers/byte_level.cc deleted file mode 100644 index e686bce3c785..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/byte_level.cc +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "fast_tokenizer/pretokenizers/byte_level.h" - -#include -#include - -#include "fast_tokenizer/utils/utf8.h" -#include "fast_tokenizer/utils/utils.h" -#include "glog/logging.h" -#include "re2/re2.h" -#include "unicode/uchar.h" - - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - - -static re2::RE2 pattern( - R"('s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+)"); - - -static std::unordered_map BYTES_TO_CHARS = - utils::CreateBytesToChars(); -ByteLevelPreTokenizer::ByteLevelPreTokenizer(bool add_prefix_space, - bool use_regex) - : add_prefix_space_(add_prefix_space), use_regex_(use_regex) {} - - -void ByteLevelPreTokenizer::operator()(PreTokenizedString* pretokenized) const { - std::vector normalized_splits; - pretokenized->Split([&normalized_splits, this]( - int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - if (this->add_prefix_space_ && normalized->GetStr().find(' ') != 0) { - normalized->Prepend(" "); - } - if (this->use_regex_) { - normalized->Split(pattern, core::SplitMode::ISOLATED, &normalized_splits); - for (auto&& normalize : normalized_splits) { - if (!normalize.IsEmpty()) { - string_splits->emplace_back(std::move(normalize)); - } - } - } else { - string_splits->emplace_back(*normalized); - } - }); - pretokenized->Normalize([](normalizers::NormalizedString* normalized) { - const std::string& str = normalized->GetStr(); - std::u32string u32normalized; - std::vector changes; - size_t utf8_len = 0; - uint32_t last_char; - uint32_t curr_char; - while (utf8_len < str.length()) { - auto chwidth = utils::UTF8ToUInt32(str.data() + utf8_len, &curr_char); - curr_char = utils::UTF8ToUnicode(curr_char); - for (int i = 0; i < chwidth; ++i) { - u32normalized.push_back(BYTES_TO_CHARS.at(str[i + utf8_len])); - if (i == 0) { - changes.push_back(0); - } else { - changes.push_back(1); - } - } - utf8_len += chwidth; - } - normalized->UpdateNormalized({u32normalized, changes}, 0); - }); -} - - -void to_json(nlohmann::json& j, - const ByteLevelPreTokenizer& byte_pre_tokenizer) { - j = { - {"type", "ByteLevelPreTokenizer"}, - {"add_prefix_space", byte_pre_tokenizer.add_prefix_space_}, - {"use_regex", byte_pre_tokenizer.use_regex_}, - }; -} - - -void from_json(const nlohmann::json& j, - ByteLevelPreTokenizer& byte_pre_tokenizer) { - j.at("add_prefix_space").get_to(byte_pre_tokenizer.add_prefix_space_); - j.at("use_regex").get_to(byte_pre_tokenizer.add_prefix_space_); -} - -void ProcessOffsets(core::Encoding* encoding, bool add_prefix_space) { - auto process_token_fn = - [&](uint32_t i, const std::string& token, core::Offset* offset) -> void { - uint32_t leading_spaces = 0; - uint32_t trailing_spaces = 0; - - std::wstring_convert, char32_t> conv; - std::u32string u32token = conv.from_bytes(token); - for (int i = 0; i < u32token.size(); ++i) { - if (utils::IsWhiteSpace(u32token[i]) || - u32token[i] == BYTES_TO_CHARS.at(' ')) { - ++leading_spaces; - } else { - break; - } - } - - for (int i = u32token.size() - 1; i >= 0; --i) { - if (utils::IsWhiteSpace(u32token[i]) || - u32token[i] == BYTES_TO_CHARS.at(' ')) { - ++trailing_spaces; - } else { - break; - } - } - - if (leading_spaces > 0 || trailing_spaces > 0) { - if (leading_spaces > 0) { - bool is_first = (i == 0) || (offset->first == 0); - if (is_first && add_prefix_space && leading_spaces == 1) { - leading_spaces = 0; - } - offset->first = - (std::min)(offset->first + leading_spaces, offset->second); - } - } - if (trailing_spaces > 0 && offset->second >= trailing_spaces) { - offset->second = - (std::max)(offset->second - trailing_spaces, offset->first); - } - }; - encoding->ProcessTokenWithOffsets(process_token_fn); -} - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/byte_level.h b/fast_tokenizer/fast_tokenizer/pretokenizers/byte_level.h deleted file mode 100644 index c06dcc373f6d..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/byte_level.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -struct FASTTOKENIZER_DECL ByteLevelPreTokenizer : public PreTokenizer { - ByteLevelPreTokenizer(bool add_prefix_space = true, bool use_regex = true); - virtual void operator()(PreTokenizedString* pretokenized) const override; - friend void to_json(nlohmann::json& j, - const ByteLevelPreTokenizer& byte_pre_tokenizer); - friend void from_json(const nlohmann::json& j, - ByteLevelPreTokenizer& byte_pre_tokenizer); - -private: - bool add_prefix_space_; - bool use_regex_; -}; - -void FASTTOKENIZER_DECL ProcessOffsets(core::Encoding* encoding, - bool add_prefix_space); - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/metaspace.cc b/fast_tokenizer/fast_tokenizer/pretokenizers/metaspace.cc deleted file mode 100644 index f3a26001f11b..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/metaspace.cc +++ /dev/null @@ -1,87 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pretokenizers/metaspace.h" - -#include "fast_tokenizer/utils/utf8.h" -#include "glog/logging.h" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -static re2::RE2 pattern(" "); - -void MetaSpacePreTokenizer::UpdateReplacementChar() { - uint32_t ch; - utils::UTF8ToUInt32(replacement_.data(), &ch); - replacement_char_ = utils::UTF8ToUnicode(ch); -} - -MetaSpacePreTokenizer::MetaSpacePreTokenizer(const std::string& replacement, - bool add_prefix_space) - : replacement_(replacement), add_prefix_space_(add_prefix_space) { - UpdateReplacementChar(); -} - -std::string MetaSpacePreTokenizer::GetReplacement() const { - return replacement_; -} - -void MetaSpacePreTokenizer::SetReplacement(const std::string& replacement) { - replacement_ = replacement; - UpdateReplacementChar(); -} - -void MetaSpacePreTokenizer::operator()(PreTokenizedString* pretokenized) const { - std::vector normalized_splits; - pretokenized->Split([&](int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - normalized->Replace(pattern, replacement_); - if (add_prefix_space_ && normalized->GetStr().find(replacement_) != 0) { - normalized->Prepend(replacement_); - } - normalized->Split( - [&](char32_t ch) -> bool { return ch == replacement_char_; }, - core::SplitMode::MERGED_WITH_NEXT, - &normalized_splits); - for (auto&& normalize : normalized_splits) { - if (!normalize.IsEmpty()) { - VLOG(6) << "After pretokenized: " << normalize.GetStr(); - string_splits->emplace_back(std::move(normalize)); - } - } - }); -} - -void to_json(nlohmann::json& j, - const MetaSpacePreTokenizer& meta_pretokenizer) { - j = { - {"type", "MetaSpacePreTokenizer"}, - {"replacement", meta_pretokenizer.replacement_}, - {"add_prefix_space", meta_pretokenizer.add_prefix_space_}, - }; -} - -void from_json(const nlohmann::json& j, - MetaSpacePreTokenizer& meta_pretokenizer) { - j.at("add_prefix_space").get_to(meta_pretokenizer.add_prefix_space_); - meta_pretokenizer.SetReplacement(j.at("replacement").get()); -} - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/metaspace.h b/fast_tokenizer/fast_tokenizer/pretokenizers/metaspace.h deleted file mode 100644 index 4b5c20504d3a..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/metaspace.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -struct FASTTOKENIZER_DECL MetaSpacePreTokenizer : public PreTokenizer { - // Replaces white space with U+2581 (LOWER ONE EIGHT BLOCK) - MetaSpacePreTokenizer(const std::string& replacement = "\xe2\x96\x81", - bool add_prefix_space = true); - MetaSpacePreTokenizer(const MetaSpacePreTokenizer&) = default; - virtual void operator()(PreTokenizedString* pretokenized) const override; - std::string GetReplacement() const; - void SetReplacement(const std::string&); - -private: - void UpdateReplacementChar(); - std::string replacement_; - bool add_prefix_space_; - char32_t replacement_char_; - - friend void to_json(nlohmann::json& j, - const MetaSpacePreTokenizer& meta_pretokenizer); - friend void from_json(const nlohmann::json& j, - MetaSpacePreTokenizer& meta_pretokenizer); -}; - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizer.cc b/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizer.cc deleted file mode 100644 index 47d7b5f6ed50..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizer.cc +++ /dev/null @@ -1,277 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pretokenizers/pretokenizer.h" - -#include -#include -#include - -#include "fast_tokenizer/utils/unique_ptr.h" -#include "fast_tokenizer/utils/utf8.h" -#include "glog/logging.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -BytesToCharOffsetConverter::BytesToCharOffsetConverter(const std::string& seq) - : OffsetConverter(seq) { - std::wstring_convert, char32_t> conv; - std::u32string u32seq = conv.from_bytes(seq); - offset_map_.reserve(u32seq.length() * 4); - for (int i = 0; i < u32seq.length(); ++i) { - auto utf8_len = utils::GetUTF8CharLen(u32seq[i]); - for (int j = 0; j < utf8_len; ++j) { - offset_map_.push_back(i); - } - } -} - -bool BytesToCharOffsetConverter::convert(const core::Offset& offset, - core::Offset* result) const { - size_t byte_start = offset.first; - size_t byte_end = offset.second; - if (offset_map_.size() <= byte_start) { - return false; - } - auto char_start = offset_map_.at(byte_start); - auto char_end = char_start + 1; - if (offset_map_.size() > byte_end) { - char_end = offset_map_.at(byte_end); - } else if (offset_map_.size() > byte_end - 1) { - char_end = offset_map_.at(byte_end - 1) + 1; - } - *result = {char_start, char_end}; - return true; -} - - -CharToBytesOffsetConverter::CharToBytesOffsetConverter(const std::string& seq) - : OffsetConverter(seq) { - std::wstring_convert, char32_t> conv; - std::u32string u32seq = conv.from_bytes(seq); - uint32_t index = 0; - offset_map_.reserve(u32seq.length() * 4); - for (int i = 0; i < u32seq.length(); ++i) { - offset_map_.push_back(index); - auto utf8_len = fast_tokenizer::utils::GetUTF8CharLen(u32seq[i]); - index += utf8_len; - } - offset_map_.push_back(index); -} - -bool CharToBytesOffsetConverter::convert(const core::Offset& offset, - core::Offset* result) const { - size_t char_start = offset.first; - size_t char_end = offset.second; - if (offset_map_.size() <= char_start) { - return false; - } - auto byte_start = offset_map_.at(char_start); - auto byte_end = byte_start + 1; - if (offset_map_.size() > char_end) { - byte_end = offset_map_.at(char_end); - } - *result = {byte_start, byte_end}; - return true; -} - -PreTokenizedString::PreTokenizedString(const std::string& original) - : original_(original) { - splits_.emplace_back(std::move(StringSplit(original_))); -} - -PreTokenizedString::PreTokenizedString( - const normalizers::NormalizedString& normalized) - : original_(normalized.GetOrignalStr()) { - splits_.emplace_back(std::move(StringSplit(original_))); -} - -PreTokenizedString& PreTokenizedString::operator=(PreTokenizedString&& other) { - original_ = std::move(other.original_); - splits_ = std::move(other.splits_); - return *this; -} - -size_t PreTokenizedString::GetSplitsSize() const { return splits_.size(); } - -StringSplit PreTokenizedString::GetSplit(int idx) const { return splits_[idx]; } - -const std::string& PreTokenizedString::GetOriginStr() const { - return original_; -} - -void PreTokenizedString::Split( - std::function*)> split_fn) { - std::vector new_splits; - new_splits.reserve(splits_.size()); - for (int i = 0; i < splits_.size(); ++i) { - if (splits_[i].tokens_.size() > 0) { - new_splits.emplace_back(std::move(splits_[i])); - continue; - } - split_fn(i, &splits_[i].normalized_, &new_splits); - } - splits_ = std::move(new_splits); -} - -void PreTokenizedString::Normalize( - std::function normalize_fn) { - for (auto& split : splits_) { - if (split.tokens_.empty()) { - normalize_fn(&split.normalized_); - } - } -} -void PreTokenizedString::Tokenize( - std::function(normalizers::NormalizedString*)> - tokenize_fn) { - for (auto& split : splits_) { - if (split.tokens_.empty()) { - split.tokens_ = std::move(tokenize_fn(&split.normalized_)); - } - } -} - -bool PreTokenizedString::TransformToEncoding( - const std::vector& input_word_idx, - uint32_t type_id, - core::OffsetType offset_type, - core::Encoding* encoding) const { - if (splits_.empty()) { - *encoding = core::Encoding(); - return true; - } - for (const auto& split : splits_) { - if (split.tokens_.empty()) { - throw std::logic_error( - "The split of PreTokenizedString is empty, please call " - "PreTokenizedString::Tokenize first before transform to Encoding."); - return false; - } - } - - if (offset_type == core::OffsetType::CHAR) { - return TransformToEncodingUseConvertor( - input_word_idx, type_id, encoding); - } - return TransformToEncodingUseConvertor( - input_word_idx, type_id, encoding); -} - -template -bool PreTokenizedString::TransformToEncodingUseConvertor( - const std::vector& input_word_idx, - uint32_t type_id, - core::Encoding* encoding) const { - Convertor converter(original_); - uint32_t tokens_size = 0; - for (int i = 0; i < splits_.size(); ++i) { - tokens_size += splits_[i].tokens_.size(); - } - - std::vector token_ids(tokens_size); - std::vector tokens(tokens_size); - std::vector offsets(tokens_size); - uint32_t curr_idx = 0; - for (int i = 0; i < splits_.size(); ++i) { - const auto& split = splits_[i]; - const auto& normalized = split.normalized_; - auto offset = normalized.GetOrginalOffset(); - core::Offset tmp_offset; - bool has_set_offset = false; - for (const auto& token : split.tokens_) { - auto token_offset = token.offset_; - bool flag = normalized.ConvertOffsets(&token_offset, false); - if (flag) { - token_offset.first += offset.first; - token_offset.second += offset.first; - } - if (has_set_offset) { - offset = token_offset; - has_set_offset = true; - } - converter.convert(token_offset, &tmp_offset); - token_ids[curr_idx] = token.id_; - tokens[curr_idx] = token.value_; - offsets[curr_idx] = tmp_offset; - ++curr_idx; - } - } - // Setting words_idx - std::vector words_idx(tokens_size); - if (input_word_idx.size() == 0) { - uint32_t word_offset = 0; - for (uint32_t i = 0; i < splits_.size(); ++i) { - std::fill_n( - words_idx.begin() + word_offset, splits_[i].tokens_.size(), i); - word_offset += splits_[i].tokens_.size(); - } - } else { - std::fill(words_idx.begin(), words_idx.end(), input_word_idx[0]); - } - *encoding = std::move(core::Encoding( - std::move(token_ids), - std::vector(tokens_size, type_id), // type_ids - std::move(tokens), - std::move(words_idx), - std::move(offsets), - std::vector(tokens_size, 0), /* special_tokens_mask */ - std::vector(tokens_size, 1), /* attention_mask */ - std::vector(), /* overflowing */ - std::unordered_map() /* sequence_ranges */)); - return true; -} - -void PreTokenizedString::SetOriginalStr(const std::string& original) { - original_ = original; - splits_.clear(); - splits_.emplace_back(original_); -} - -std::vector>> -PreTokenizedString::GetSplits(bool is_original, - const core::OffsetType& offset_type) const { - std::unique_ptr converter; - if (offset_type == core::OffsetType::BYTE) { - converter = utils::make_unique(original_); - } else { - converter = utils::make_unique(original_); - } - std::vector>> - result; - uint32_t offset = 0; - for (auto&& split : splits_) { - core::Offset curr_offset, split_offset; - if (is_original) { - split_offset = split.normalized_.GetOrginalOffset(); - } else { - auto len = split.normalized_.GetLen(); - offset += len; - split_offset = {offset - len, offset}; - } - - // Convert to char offsets if relevant - converter->convert(split_offset, &curr_offset); - result.emplace_back(split.normalized_.GetStr(), curr_offset, split.tokens_); - } - return result; -} - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizer.h b/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizer.h deleted file mode 100644 index 02abfff8949c..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizer.h +++ /dev/null @@ -1,117 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include -#include -#include - -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -struct FASTTOKENIZER_DECL StringSplit { - normalizers::NormalizedString normalized_; - std::vector tokens_; - StringSplit(normalizers::NormalizedString&& normalized) - : normalized_(std::move(normalized)) {} - StringSplit(const normalizers::NormalizedString& normalized) - : normalized_(normalized) {} - StringSplit(const normalizers::NormalizedString& normalized, - const std::vector& tokens) - : normalized_(normalized), tokens_(tokens) {} - StringSplit() = default; - StringSplit(const StringSplit& other) = default; - StringSplit(StringSplit&& other) - : tokens_(std::move(other.tokens_)), - normalized_(std::move(other.normalized_)) {} - - StringSplit& operator=(const StringSplit& other) = default; - StringSplit& operator=(StringSplit&& other) { - tokens_ = std::move(other.tokens_); - normalized_ = std::move(other.normalized_); - return *this; - } -}; - -class FASTTOKENIZER_DECL PreTokenizedString { -public: - PreTokenizedString() = default; - PreTokenizedString(const std::string& original); - PreTokenizedString(const normalizers::NormalizedString& normalized); - PreTokenizedString& operator=(PreTokenizedString&& other); - - void Split(std::function*)> split_fn); - void Normalize( - std::function normalize_fn); - // For wordpiece, bpe ...... - void Tokenize( - std::function(normalizers::NormalizedString*)> - tokenize_fn); - bool TransformToEncoding(const std::vector& word_idx, - uint32_t type_id, - core::OffsetType offset_type, - core::Encoding* encodings) const; - template - bool TransformToEncodingUseConvertor(const std::vector& word_idx, - uint32_t type_id, - core::Encoding* encodings) const; - size_t GetSplitsSize() const; - StringSplit GetSplit(int idx) const; - const std::string& GetOriginStr() const; - void SetOriginalStr(const std::string& original); - std::vector>> - GetSplits(bool is_original, const core::OffsetType& offset_type) const; - -private: - std::string original_; - std::vector splits_; -}; - -struct FASTTOKENIZER_DECL PreTokenizer { - virtual void operator()(PreTokenizedString* pretokenized) const = 0; -}; - -struct FASTTOKENIZER_DECL OffsetConverter { - OffsetConverter(const std::string&) {} - virtual bool convert(const core::Offset&, core::Offset*) const { - return true; - } -}; - -struct FASTTOKENIZER_DECL BytesToCharOffsetConverter : public OffsetConverter { - std::vector offset_map_; - BytesToCharOffsetConverter(const std::string&); - virtual bool convert(const core::Offset&, core::Offset*) const; -}; - -struct FASTTOKENIZER_DECL CharToBytesOffsetConverter : public OffsetConverter { - std::vector offset_map_; - CharToBytesOffsetConverter(const std::string&); - virtual bool convert(const core::Offset&, core::Offset*) const; -}; - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizers.h b/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizers.h deleted file mode 100644 index 5828ecd2dafe..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/pretokenizers.h +++ /dev/null @@ -1,24 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/pretokenizers/bert.h" -#include "fast_tokenizer/pretokenizers/byte_level.h" -#include "fast_tokenizer/pretokenizers/metaspace.h" -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "fast_tokenizer/pretokenizers/sequence.h" -#include "fast_tokenizer/pretokenizers/split.h" -#include "fast_tokenizer/pretokenizers/whitespace.h" -#include "fast_tokenizer/pretokenizers/whitespace_and_punctuation.h" diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/sequence.cc b/fast_tokenizer/fast_tokenizer/pretokenizers/sequence.cc deleted file mode 100644 index 3ac87ddbc350..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/sequence.cc +++ /dev/null @@ -1,122 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pretokenizers/pretokenizers.h" -#include "glog/logging.h" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -SequencePreTokenizer::SequencePreTokenizer( - const std::vector& pretokenizers) { - for (auto& pretokenizer : pretokenizers) { - AppendPreTokenizer(pretokenizer); - } -} - -void SequencePreTokenizer::AppendPreTokenizer(PreTokenizer* pretokenizer) { - std::shared_ptr pretokenizer_ptr; - if (typeid(*pretokenizer) == typeid(SequencePreTokenizer)) { - auto cast_pretokenizer = dynamic_cast(pretokenizer); - pretokenizer_ptr = - std::make_shared(*cast_pretokenizer); - } else if (typeid(*pretokenizer) == typeid(BertPreTokenizer)) { - auto cast_pretokenizer = dynamic_cast(pretokenizer); - pretokenizer_ptr = std::make_shared(*cast_pretokenizer); - } else if (typeid(*pretokenizer) == typeid(MetaSpacePreTokenizer)) { - auto cast_pretokenizer = dynamic_cast(pretokenizer); - pretokenizer_ptr = - std::make_shared(*cast_pretokenizer); - } else if (typeid(*pretokenizer) == typeid(WhitespacePreTokenizer)) { - auto cast_pretokenizer = - dynamic_cast(pretokenizer); - pretokenizer_ptr = - std::make_shared(*cast_pretokenizer); - } else if (typeid(*pretokenizer) == - typeid(WhitespaceAndPunctuationPreTokenizer)) { - auto cast_pretokenizer = - dynamic_cast(pretokenizer); - pretokenizer_ptr = std::make_shared( - *cast_pretokenizer); - } else if (typeid(*pretokenizer) == typeid(SplitPreTokenizer)) { - auto cast_pretokenizer = dynamic_cast(pretokenizer); - pretokenizer_ptr = std::make_shared(*cast_pretokenizer); - } else if (typeid(*pretokenizer) == typeid(ByteLevelPreTokenizer)) { - auto cast_pretokenizer = dynamic_cast(pretokenizer); - pretokenizer_ptr = - std::make_shared(*cast_pretokenizer); - } else { - VLOG(6) << "This pretokenizer is not supportted now."; - } - pretokenzer_ptrs_.push_back(pretokenizer_ptr); -} - -void SequencePreTokenizer::operator()(PreTokenizedString* pretokenized) const { - for (auto& pretokenizer : pretokenzer_ptrs_) { - pretokenizer->operator()(pretokenized); - } -} - -void to_json(nlohmann::json& j, - const SequencePreTokenizer& sequence_pretokenizer) { - nlohmann::json jlist; - for (auto& ptr : sequence_pretokenizer.pretokenzer_ptrs_) { - nlohmann::json jitem; - if (typeid(*ptr) == typeid(SequencePreTokenizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(BertPreTokenizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(MetaSpacePreTokenizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(WhitespacePreTokenizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(WhitespaceAndPunctuationPreTokenizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(SplitPreTokenizer)) { - jitem = *dynamic_cast(ptr.get()); - } else if (typeid(*ptr) == typeid(ByteLevelPreTokenizer)) { - jitem = *dynamic_cast(ptr.get()); - } - jlist.push_back(jitem); - } - j = {{"type", "SequencePreTokenizer"}, {"pretokenizers", jlist}}; -} - -void from_json(const nlohmann::json& j, - SequencePreTokenizer& sequence_pretokenizer) { -#define TRY_APPEND_PRETOKENIZER(PRETOKENIZER_TYPE) \ - if (pretokenizer_type == #PRETOKENIZER_TYPE) { \ - PRETOKENIZER_TYPE pretokenizer; \ - pretokenizer_json.get_to(pretokenizer); \ - sequence_pretokenizer.AppendPreTokenizer(&pretokenizer); \ - } - for (auto& pretokenizer_json : j.at("pretokenizers")) { - std::string pretokenizer_type; - pretokenizer_json.at("type").get_to(pretokenizer_type); - TRY_APPEND_PRETOKENIZER(SequencePreTokenizer); - TRY_APPEND_PRETOKENIZER(WhitespacePreTokenizer); - TRY_APPEND_PRETOKENIZER(WhitespaceAndPunctuationPreTokenizer); - TRY_APPEND_PRETOKENIZER(MetaSpacePreTokenizer); - TRY_APPEND_PRETOKENIZER(BertPreTokenizer); - TRY_APPEND_PRETOKENIZER(ByteLevelPreTokenizer); - TRY_APPEND_PRETOKENIZER(SplitPreTokenizer); - } -#undef TRY_APPEND_PRETOKENIZER -} - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/sequence.h b/fast_tokenizer/fast_tokenizer/pretokenizers/sequence.h deleted file mode 100644 index 741f8d43c08a..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/sequence.h +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include - -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "fast_tokenizer/utils/utils.h" -#include "nlohmann/json.hpp" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -struct FASTTOKENIZER_DECL SequencePreTokenizer : public PreTokenizer { - SequencePreTokenizer() = default; - SequencePreTokenizer(const SequencePreTokenizer&) = default; - SequencePreTokenizer(const std::vector& pretokenizers); - virtual void operator()(PreTokenizedString* pretokenized) const override; - void AppendPreTokenizer(PreTokenizer* pretokenizer); - -private: - std::vector> pretokenzer_ptrs_; - friend void to_json(nlohmann::json& j, - const SequencePreTokenizer& sequence_pretokenizer); - friend void from_json(const nlohmann::json& j, - SequencePreTokenizer& sequence_pretokenizer); -}; - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/split.cc b/fast_tokenizer/fast_tokenizer/pretokenizers/split.cc deleted file mode 100644 index 927af338b2e2..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/split.cc +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pretokenizers/split.h" - -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/normalizers/normalizer.h" -#include "fast_tokenizer/utils/unique_ptr.h" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -SplitPreTokenizer::SplitPreTokenizer( - const SplitPreTokenizer& split_pretokenizer) - : pattern_(new re2::RE2(split_pretokenizer.pattern_->pattern())) { - split_mode_ = split_pretokenizer.split_mode_; - invert_ = split_pretokenizer.invert_; -} - -SplitPreTokenizer::SplitPreTokenizer(const std::string& pattern, - core::SplitMode split_mode, - bool invert) - : invert_(invert), split_mode_(split_mode) { - pattern_ = utils::make_unique(pattern); -} - -void SplitPreTokenizer::operator()(PreTokenizedString* pretokenized) const { - pretokenized->Split([&](int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - std::vector normalized_splits; - normalized->Split(*pattern_, split_mode_, &normalized_splits, invert_); - for (auto& normalize : normalized_splits) { - string_splits->push_back(StringSplit(normalize)); - } - }); -} - - -void to_json(nlohmann::json& j, const SplitPreTokenizer& split_pretokenizer) { - j = { - {"type", "SplitPreTokenizer"}, - {"pattern", split_pretokenizer.pattern_->pattern()}, - {"split_mode", split_pretokenizer.split_mode_}, - {"invert", split_pretokenizer.invert_}, - }; -} - -void from_json(const nlohmann::json& j, SplitPreTokenizer& split_pretokenizer) { - split_pretokenizer.pattern_ = - utils::make_unique(j.at("pattern").get()); - j.at("split_mode").get_to(split_pretokenizer.split_mode_); - j.at("invert").get_to(split_pretokenizer.invert_); -} - - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/split.h b/fast_tokenizer/fast_tokenizer/pretokenizers/split.h deleted file mode 100644 index 52796f9f4e24..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/split.h +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace re2 { -class RE2; -} // namespace re2 - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -struct FASTTOKENIZER_DECL SplitPreTokenizer : public PreTokenizer { - SplitPreTokenizer() = default; - SplitPreTokenizer(const std::string& pattern, - core::SplitMode split_mode, - bool invert); - SplitPreTokenizer(const SplitPreTokenizer& split_pretokenizer); - virtual void operator()(PreTokenizedString* pretokenized) const override; - friend void to_json(nlohmann::json& j, - const SplitPreTokenizer& split_pretokenizer); - friend void from_json(const nlohmann::json& j, - SplitPreTokenizer& split_pretokenizer); - -private: - bool invert_; - core::SplitMode split_mode_; - std::unique_ptr pattern_; -}; - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace.cc b/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace.cc deleted file mode 100644 index 6ef950870997..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace.cc +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pretokenizers/whitespace.h" - -#include "fast_tokenizer/normalizers/normalizer.h" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { -static re2::RE2 pattern("[\\s\\p{Zs}]+"); - -void WhitespacePreTokenizer::operator()( - PreTokenizedString* pretokenized) const { - pretokenized->Split([&](int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - std::vector normalized_splits; - normalized->Split(pattern, core::SplitMode::REMOVED, &normalized_splits); - for (auto& normalize : normalized_splits) { - string_splits->push_back(StringSplit(normalize)); - } - }); -} - -void to_json(nlohmann::json& j, - const WhitespacePreTokenizer& whitespace_pretokenizer) { - j = { - {"type", "WhitespacePreTokenizer"}, - }; -} - -void from_json(const nlohmann::json& j, - WhitespacePreTokenizer& whitespace_pretokenizer) {} - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace.h b/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace.h deleted file mode 100644 index 43aa955ffc06..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace.h +++ /dev/null @@ -1,34 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -struct FASTTOKENIZER_DECL WhitespacePreTokenizer : public PreTokenizer { - virtual void operator()(PreTokenizedString* pretokenized) const override; - friend void to_json(nlohmann::json& j, - const WhitespacePreTokenizer& whitespace_pretokenizer); - friend void from_json(const nlohmann::json& j, - WhitespacePreTokenizer& whitespace_pretokenizer); -}; - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace_and_punctuation.cc b/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace_and_punctuation.cc deleted file mode 100644 index d54d59cdbcfc..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace_and_punctuation.cc +++ /dev/null @@ -1,60 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pretokenizers/whitespace_and_punctuation.h" - -#include "fast_tokenizer/normalizers/normalizer.h" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { -static re2::RE2 pattern("[\\s\\p{Zs}]+"); - -void WhitespaceAndPunctuationPreTokenizer::operator()( - PreTokenizedString* pretokenized) const { - pretokenized->Split([&](int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - std::vector normalized_splits; - normalized->Split(pattern, core::SplitMode::REMOVED, &normalized_splits); - for (auto& normalize : normalized_splits) { - string_splits->push_back(StringSplit(normalize)); - } - }); - pretokenized->Split([&](int idx, - normalizers::NormalizedString* normalized, - std::vector* string_splits) { - std::vector normalized_splits; - normalized->Split("\\w+", core::SplitMode::ISOLATED, &normalized_splits); - for (auto& normalize : normalized_splits) { - string_splits->push_back(StringSplit(normalize)); - } - }); -} - -void to_json( - nlohmann::json& j, - const WhitespaceAndPunctuationPreTokenizer& whitespace_pretokenizer) { - j = { - {"type", "WhitespaceAndPunctuationPreTokenizer"}, - }; -} - -void from_json(const nlohmann::json& j, - WhitespaceAndPunctuationPreTokenizer& whitespace_pretokenizer) {} - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace_and_punctuation.h b/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace_and_punctuation.h deleted file mode 100644 index f7343052e585..000000000000 --- a/fast_tokenizer/fast_tokenizer/pretokenizers/whitespace_and_punctuation.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include "fast_tokenizer/pretokenizers/pretokenizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pretokenizers { - -struct FASTTOKENIZER_DECL WhitespaceAndPunctuationPreTokenizer - : public PreTokenizer { - virtual void operator()(PreTokenizedString* pretokenized) const override; - friend void to_json( - nlohmann::json& j, - const WhitespaceAndPunctuationPreTokenizer& whitespace_pretokenizer); - friend void from_json( - const nlohmann::json& j, - WhitespaceAndPunctuationPreTokenizer& whitespace_pretokenizer); -}; - -} // namespace pretokenizers -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/pybind/CMakeLists.txt deleted file mode 100644 index 66e6290ddd96..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-rpath='$ORIGIN'") -cc_library(pybind_utils SRCS utils.cc DEPS pybind python json) -cc_library(pybind_normalizers SRCS normalizers.cc DEPS pybind python json normalizers) -cc_library(pybind_pretokenizers SRCS pretokenizers.cc DEPS pybind python json pretokenizers) -cc_library(pybind_models SRCS models.cc DEPS pybind python json models) -cc_library(pybind_postprocessors SRCS postprocessors.cc DEPS pybind python core json postprocessors) -cc_library(pybind_tokenizers SRCS tokenizers.cc DEPS pybind python pybind_utils json tokenizer) -cc_library(pybind_exception SRCS exception.cc DEPS pybind python) -cc_library(pybind_decoders SRCS decoders.cc DEPS pybind python json decoders) -cc_library(pybind_core SRCS core.cc DEPS pybind python json) \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/pybind/core.cc b/fast_tokenizer/fast_tokenizer/pybind/core.cc deleted file mode 100644 index 26cd25d9317d..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/core.cc +++ /dev/null @@ -1,288 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include - -#include "fast_tokenizer/core/added_vocabulary.h" -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/pybind/core.h" - -#include -#include - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -py::list GetWordIdx(const core::Encoding& self) { - py::list list; - for (const auto& idx : self.GetWordsIdx()) { - if (idx == static_cast(-1)) { - list.append(py::none()); - } else { - list.append(py::cast(idx)); - } - } - return list; -} - -void BindCore(pybind11::module* m) { - py::class_(*m, "Token") - .def(py::init<>()) - .def_readwrite("id", &core::Token::id_) - .def_readwrite("value", &core::Token::value_) - .def_readwrite("offset", &core::Token::offset_) - .def("__repr__", [](const core::Token& token) { - std::ostringstream oss; - oss << "id: " << token.id_ << "\tvalue:" << token.value_ - << "\toffset: (" << token.offset_.first << ", " - << token.offset_.second << ")"; - return oss.str(); - }); - py::class_(*m, "PadMethod") - .def(py::init<>()) - .def_readwrite("strategy", &core::PadMethod::strategy_) - .def_readwrite("direction", &core::PadMethod::direction_) - .def_readwrite("pad_id", &core::PadMethod::pad_id_) - .def_readwrite("pad_token_type_id", &core::PadMethod::pad_token_type_id_) - .def_readwrite("pad_token", &core::PadMethod::pad_token_) - .def_readwrite("pad_len", &core::PadMethod::pad_len_) - .def_readwrite("pad_to_multiple_of", - &core::PadMethod::pad_to_multiple_of_); - py::class_(*m, "TruncMethod") - .def(py::init<>()) - .def_readwrite("direction", &core::TruncMethod::direction_) - .def_readwrite("max_len", &core::TruncMethod::max_len_) - .def_readwrite("strategy", &core::TruncMethod::strategy_) - .def_readwrite("stride", &core::TruncMethod::stride_); - - py::enum_(*m, "OffsetType") - .value("CHAR", core::OffsetType::CHAR) - .value("BYTE", core::OffsetType::BYTE) - .export_values(); - py::enum_(*m, "Direction") - .value("LEFT", core::Direction::LEFT) - .value("RIGHT", core::Direction::RIGHT) - .export_values(); - py::enum_(*m, "TruncStrategy") - .value("LONGEST_FIRST", core::TruncStrategy::LONGEST_FIRST) - .value("ONLY_FIRST", core::TruncStrategy::ONLY_FIRST) - .value("ONLY_SECOND", core::TruncStrategy::ONLY_SECOND) - .export_values(); - py::enum_(*m, "PadStrategy") - .value("BATCH_LONGEST", core::PadStrategy::BATCH_LONGEST) - .value("FIXED_SIZE", core::PadStrategy::FIXED_SIZE) - .export_values(); - - py::enum_(*m, "SplitMode") - .value("REMOVED", core::SplitMode::REMOVED) - .value("ISOLATED", core::SplitMode::ISOLATED) - .value("MERGED_WITH_PREVIOUS", core::SplitMode::MERGED_WITH_PREVIOUS) - .value("MERGED_WITH_NEXT", core::SplitMode::MERGED_WITH_NEXT) - .value("CONTIGUOUS", core::SplitMode::CONTIGUOUS) - .export_values(); - - py::class_(*m, "Encoding") - .def(py::init&, - const std::vector&, - const std::vector&, - const std::vector&, - const std::vector&, - const std::vector&, - const std::vector&, - const std::vector&, - const std::unordered_map&>(), - py::arg("ids"), - py::arg("type_ids"), - py::arg("tokens"), - py::arg("words_idx"), - py::arg("offsets"), - py::arg("special_tokens_mask"), - py::arg("attention_mask"), - py::arg("overflowing"), - py::arg("sequence_ranges")) - .def(py::init(), py::arg("size")) - .def(py::init&, uint32_t>(), - py::arg("tokens"), - py::arg("type_id")) - .def("__str__", &core::Encoding::DebugString) - .def("__repr__", &core::Encoding::DebugString) - .def("__len__", &core::Encoding::GetLen) - .def_property_readonly("n_sequences", &core::Encoding::GetNumSequence) - .def_property_readonly("tokens", &core::Encoding::GetTokens) - .def_property_readonly("word_ids", &GetWordIdx) - .def_property_readonly("sequence_ids", &core::Encoding::GetSequenceIds) - .def_property_readonly("ids", &core::Encoding::GetIds) - .def_property_readonly("type_ids", &core::Encoding::GetTypeIds) - .def_property_readonly("offsets", &core::Encoding::GetOffsets) - .def_property_readonly("special_tokens_mask", - &core::Encoding::GetSpecialTokensMask) - .def_property_readonly("attention_mask", - &core::Encoding::GetAttentionMask) - .def_property_readonly("overflowing", &core::Encoding::GetOverflowing) - .def("set_sequence_ids", - &core::Encoding::SetSequenceIds, - py::arg("sequence_id")) - .def("char_to_token", - [](const core::Encoding& self, - uint32_t char_pos, - uint32_t seq_id) -> py::object { - auto token_idxs = self.CharOffsetsToTokenIdx(char_pos, seq_id); - if (token_idxs.size() == 0) { - return py::none(); - } - return py::cast(token_idxs[0]); - }, - py::arg("char_pos"), - py::arg("sequence_index") = 0) - .def("char_to_word", - [](const core::Encoding& self, - uint32_t char_pos, - uint32_t seq_id) -> py::object { - auto word_idxs = self.CharOffsetsToWordIdx(char_pos, seq_id); - if (word_idxs.size() == 0) { - return py::none(); - } - return py::cast(word_idxs[0]); - }, - py::arg("char_pos"), - py::arg("sequence_index") = 0) - .def_static("merge", - &core::Encoding::Merge, - py::arg("encodings"), - py::arg("growing_offsets") = true) - .def("pad", - [](core::Encoding& self, - uint32_t length, - const std::string& direction, - uint32_t pad_id, - uint32_t pad_type_id, - const std::string& pad_token) { - core::Direction direct; - if (direction == "right") { - direct = core::Direction::RIGHT; - } else { - direct = core::Direction::LEFT; - } - self.Pad(length, pad_id, pad_type_id, pad_token, direct); - }, - py::arg("length"), - py::arg("direction") = "right", - py::arg("pad_id") = 0, - py::arg("pad_type_id") = 0, - py::arg("pad_token") = "[PAD]") - .def("token_to_chars", - [](const core::Encoding& self, uint32_t token_index) -> py::object { - auto offsets = self.TokenIdxToCharOffsets(token_index); - if (offsets.size() == 0) { - return py::none(); - } - return py::cast(offsets[0]); - }, - py::arg("token_index")) - .def("token_to_sequence", - [](const core::Encoding& self, uint32_t token_index) -> py::object { - auto seq_ids = self.TokenIdxToSequenceIds(token_index); - if (seq_ids.size() == 0) { - return py::none(); - } - return py::cast(seq_ids[0]); - }, - py::arg("token_index")) - .def("token_to_word", - [](const core::Encoding& self, uint32_t token_index) -> py::object { - auto word_idx = self.TokenIdxToWordIdx(token_index); - if (word_idx.size() == 0) { - return py::none(); - } - return py::cast(word_idx[0].second); - }, - py::arg("token_index")) - .def("word_to_chars", - [](const core::Encoding& self, - uint32_t word_index, - uint32_t sequence_index) -> py::object { - auto ranges = - self.WordIdxToCharOffsets(word_index, sequence_index); - if (ranges.size() == 0) { - return py::none(); - } - return py::cast(ranges[0]); - }, - py::arg("word_index"), - py::arg("sequence_index") = 0) - .def("word_to_tokens", - [](const core::Encoding& self, - uint32_t word_index, - uint32_t sequence_index) -> py::object { - auto ranges = self.WordIdxToTokensIdx(word_index, sequence_index); - if (ranges.size() == 0) { - return py::none(); - } - return py::cast(ranges[0]); - }, - py::arg("word_index"), - py::arg("sequence_index") = 0) - .def("truncate", - [](core::Encoding& self, - size_t max_length, - size_t stride, - const std::string& direction) { - core::Direction direct; - if (direction == "right") { - direct = core::Direction::RIGHT; - } else { - direct = core::Direction::LEFT; - } - self.Truncate(max_length, stride, direct); - }, - py::arg("max_length"), - py::arg("stride") = 0, - py::arg("direction") = "right"); - - py::class_(*m, "AddedToken") - .def(py::init<>()) - .def(py::init([](const std::string& content, - bool single_word, - bool lstrip, - bool rstrip, - bool normalized) { - return core::AddedToken( - content, !normalized, single_word, lstrip, rstrip); - }), - py::arg("content"), - py::arg("single_word") = false, - py::arg("lstrip") = false, - py::arg("rstrip") = false, - py::arg("normalized") = true) - .def(py::self == py::self) - .def_property_readonly("content", &core::AddedToken::GetContent) - .def_property_readonly("get_is_special", &core::AddedToken::GetIsSpecial) - .def_property_readonly( - "normalized", - [](const core::AddedToken& self) { return !self.GetUseNormalized(); }) - .def_property_readonly("lstrip", &core::AddedToken::GetUseLStrip) - .def_property_readonly("rstrip", &core::AddedToken::GetUseRStrip) - .def_property_readonly("single_word", &core::AddedToken::GetIsSingleWord); - - m->def("set_thread_num", &core::SetThreadNum); - m->def("get_thread_num", &core::GetThreadNum); -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/core.h b/fast_tokenizer/fast_tokenizer/pybind/core.h deleted file mode 100644 index 4d42bdd00cbf..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/core.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void BindCore(pybind11::module* m); - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/decoders.cc b/fast_tokenizer/fast_tokenizer/pybind/decoders.cc deleted file mode 100644 index 0e0bdbe8728f..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/decoders.cc +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/decoders/decoders.h" -#include -#include "fast_tokenizer/pybind/decoders.h" - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -class PyDecoder : public decoders::Decoder { -public: - using Decoder::Decoder; - virtual void operator()(const std::vector tokens, - std::string* result) const override { - PYBIND11_OVERLOAD_PURE_NAME( - void, Decoder, "__call__", operator(), tokens, result); - } -}; - -class PyWordPieceDecoder : public decoders::WordPiece { -public: - using WordPiece::WordPiece; - virtual void operator()(const std::vector tokens, - std::string* result) const override { - PYBIND11_OVERLOAD_NAME( - void, WordPiece, "__call__", operator(), tokens, result); - } -}; - -void BindDecoders(pybind11::module* m) { - auto submodule = m->def_submodule("decoders", "The decoders module"); - py::class_(submodule, "Decoder") - .def(py::init<>()) - .def("decode", - [](const decoders::Decoder& self, - const std::vector& tokens) { - std::string result; - self(tokens, &result); - return result; - }, - py::arg("tokens")); - - py::class_(submodule, "WordPiece") - .def(py::init(), - py::arg("prefix") = "##", - py::arg("cleanup") = true) - .def("decode", - [](const decoders::Decoder& self, - const std::vector& tokens) { - std::string result; - self(tokens, &result); - return result; - }, - py::arg("tokens")); -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/decoders.h b/fast_tokenizer/fast_tokenizer/pybind/decoders.h deleted file mode 100644 index 27be3049cf67..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/decoders.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void BindDecoders(pybind11::module* m); - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/exception.cc b/fast_tokenizer/fast_tokenizer/pybind/exception.cc deleted file mode 100644 index 35df7987fd54..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/exception.cc +++ /dev/null @@ -1,35 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pybind/exception.h" - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void ThrowExceptionToPython(std::exception_ptr p) { - static PyObject* EnforceNotMetException = - PyErr_NewException("tokenizer.EnforceNotMet", PyExc_Exception, NULL); - try { - if (p) std::rethrow_exception(p); - } catch (const std::runtime_error& e) { - PyErr_SetString(PyExc_RuntimeError, e.what()); - } -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/exception.h b/fast_tokenizer/fast_tokenizer/pybind/exception.h deleted file mode 100644 index 49381948179f..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/exception.h +++ /dev/null @@ -1,45 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include - -#include "pybind11/pybind11.h" -#define TOKENIZERS_TRY try { -#define TOKENIZERS_CATCH_AND_THROW_RETURN_NULL \ - } \ - catch (...) { \ - ThrowExceptionToPython(std::current_exception()); \ - return nullptr; \ - } - -#define TOKENIZERS_CATCH_AND_THROW_RETURN_NEG \ - } \ - catch (...) { \ - ThrowExceptionToPython(std::current_exception()); \ - return -1; \ - } - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void ThrowExceptionToPython(std::exception_ptr p); - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/models.cc b/fast_tokenizer/fast_tokenizer/pybind/models.cc deleted file mode 100644 index 3cee6b03f388..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/models.cc +++ /dev/null @@ -1,551 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/models/models.h" - -#include - -#include "fast_tokenizer/pybind/models.h" -#include "fast_tokenizer/pybind/utils.h" -#include "glog/logging.h" - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -class PyModel : public models::Model { -public: - using Model::Model; - virtual std::vector Tokenize( - const std::string& tokens) override { - PYBIND11_OVERLOAD_PURE_NAME( - std::vector, Model, "tokenize", Tokenize, tokens); - } - - virtual bool TokenToId(const std::string& token, - uint32_t* id) const override { - PYBIND11_OVERLOAD_PURE_NAME( - bool, Model, "token_to_id", TokenToId, token, id); - } - - virtual bool IdToToken(uint32_t id, std::string* token) const override { - PYBIND11_OVERLOAD_PURE_NAME( - bool, Model, "id_to_token", IdToToken, id, token); - } - - virtual core::Vocab GetVocab() const override { - PYBIND11_OVERLOAD_PURE_NAME(core::Vocab, Model, "get_vocab", GetVocab); - } - - virtual size_t GetVocabSize() const override { - PYBIND11_OVERLOAD_PURE_NAME(size_t, Model, "get_vocab_size", GetVocabSize); - } - - virtual std::vector Save( - const std::string& folder, - const std::string& filename_prefix) const override { - PYBIND11_OVERLOAD_PURE_NAME( - std::vector, Model, "save", Save, folder, filename_prefix); - } -}; - -class PyWordPiece : public models::WordPiece { - using WordPiece::WordPiece; - virtual std::vector Tokenize( - const std::string& tokens) override { - PYBIND11_OVERLOAD_NAME( - std::vector, WordPiece, "tokenize", Tokenize, tokens); - } - - virtual bool TokenToId(const std::string& token, - uint32_t* id) const override { - PYBIND11_OVERLOAD_NAME( - bool, WordPiece, "token_to_id", TokenToId, token, id); - } - - virtual bool IdToToken(uint32_t id, std::string* token) const override { - PYBIND11_OVERLOAD_NAME( - bool, WordPiece, "id_to_token", IdToToken, id, token); - } - - virtual core::Vocab GetVocab() const override { - PYBIND11_OVERLOAD_NAME(core::Vocab, WordPiece, "get_vocab", GetVocab); - } - - virtual size_t GetVocabSize() const override { - PYBIND11_OVERLOAD_NAME(size_t, WordPiece, "get_vocab_size", GetVocabSize); - } - - virtual std::vector Save( - const std::string& folder, - const std::string& filename_prefix) const override { - PYBIND11_OVERLOAD_NAME(std::vector, - WordPiece, - "save", - Save, - folder, - filename_prefix); - } -}; - -class PyFastWordPiece : public models::FastWordPiece { - using FastWordPiece::FastWordPiece; - virtual std::vector Tokenize( - const std::string& tokens) override { - PYBIND11_OVERLOAD_NAME( - std::vector, FastWordPiece, "tokenize", Tokenize, tokens); - } - - virtual bool TokenToId(const std::string& token, - uint32_t* id) const override { - PYBIND11_OVERLOAD_NAME( - bool, FastWordPiece, "token_to_id", TokenToId, token, id); - } - - virtual bool IdToToken(uint32_t id, std::string* token) const override { - PYBIND11_OVERLOAD_NAME( - bool, FastWordPiece, "id_to_token", IdToToken, id, token); - } - - virtual core::Vocab GetVocab() const override { - PYBIND11_OVERLOAD_NAME(core::Vocab, FastWordPiece, "get_vocab", GetVocab); - } - - virtual size_t GetVocabSize() const override { - PYBIND11_OVERLOAD_NAME( - size_t, FastWordPiece, "get_vocab_size", GetVocabSize); - } - - virtual std::vector Save( - const std::string& folder, - const std::string& filename_prefix) const override { - PYBIND11_OVERLOAD_NAME(std::vector, - FastWordPiece, - "save", - Save, - folder, - filename_prefix); - } -}; - -class PyBPE : public models::BPE { - using BPE::BPE; - virtual std::vector Tokenize( - const std::string& tokens) override { - PYBIND11_OVERLOAD_NAME( - std::vector, BPE, "tokenize", Tokenize, tokens); - } - - virtual bool TokenToId(const std::string& token, - uint32_t* id) const override { - PYBIND11_OVERLOAD_NAME(bool, BPE, "token_to_id", TokenToId, token, id); - } - - virtual bool IdToToken(uint32_t id, std::string* token) const override { - PYBIND11_OVERLOAD_NAME(bool, BPE, "id_to_token", IdToToken, id, token); - } - - virtual core::Vocab GetVocab() const override { - PYBIND11_OVERLOAD_NAME(core::Vocab, BPE, "get_vocab", GetVocab); - } - - virtual size_t GetVocabSize() const override { - PYBIND11_OVERLOAD_NAME(size_t, BPE, "get_vocab_size", GetVocabSize); - } - - virtual std::vector Save( - const std::string& folder, - const std::string& filename_prefix) const override { - PYBIND11_OVERLOAD_NAME( - std::vector, BPE, "save", Save, folder, filename_prefix); - } -}; - -class PyUnigram : public models::Unigram { - using Unigram::Unigram; - virtual std::vector Tokenize( - const std::string& tokens) override { - PYBIND11_OVERLOAD_NAME( - std::vector, Unigram, "tokenize", Tokenize, tokens); - } - - virtual bool TokenToId(const std::string& token, - uint32_t* id) const override { - PYBIND11_OVERLOAD_NAME(bool, Unigram, "token_to_id", TokenToId, token, id); - } - - virtual bool IdToToken(uint32_t id, std::string* token) const override { - PYBIND11_OVERLOAD_NAME(bool, Unigram, "id_to_token", IdToToken, id, token); - } - - virtual core::Vocab GetVocab() const override { - PYBIND11_OVERLOAD_NAME(core::Vocab, Unigram, "get_vocab", GetVocab); - } - - virtual size_t GetVocabSize() const override { - PYBIND11_OVERLOAD_NAME(size_t, Unigram, "get_vocab_size", GetVocabSize); - } - - virtual std::vector Save( - const std::string& folder, - const std::string& filename_prefix) const override { - PYBIND11_OVERLOAD_NAME(std::vector, - Unigram, - "save", - Save, - folder, - filename_prefix); - } -}; - -void BindModels(pybind11::module* m) { - auto submodule = m->def_submodule("models", "The models module"); - py::class_(submodule, "Model") - .def(py::init<>()) - .def("tokenize", &models::Model::Tokenize) - .def("token_to_id", &models::Model::TokenToId) - .def("id_to_token", &models::Model::IdToToken) - .def("get_vocab", &models::Model::GetVocab) - .def("get_vocab_size", &models::Model::GetVocabSize) - .def("save", &models::Model::Save); - py::class_(submodule, "WordPiece") - .def(py::init<>()) - .def(py::init(), - py::arg("vocab"), - py::arg("unk_token") = "[UNK]", - py::arg("max_input_chars_per_word") = 100, - py::arg("continuing_subword_prefix") = "##", - py::arg("handle_chinese_chars") = true) - .def("tokenize", &models::WordPiece::Tokenize) - .def("token_to_id", - [](const models::WordPiece& wordpiece, const std::string& token) { - uint32_t id; - wordpiece.TokenToId(token, &id); - return id; - }) - .def("id_to_token", - [](const models::WordPiece& wordpiece, uint32_t id) { - std::string token; - wordpiece.IdToToken(id, &token); - return token; - }) - .def("get_vocab", &models::WordPiece::GetVocab) - .def("get_vocab_size", &models::WordPiece::GetVocabSize) - .def_static( - "read_file", &models::WordPiece::GetVocabFromFile, py::arg("vocab")) - .def_static("from_file", - &models::WordPiece::GetWordPieceFromFile, - py::arg("vocab"), - py::arg("unk_token") = "[UNK]", - py::arg("max_input_chars_per_word") = 100, - py::arg("continuing_subword_prefix") = "##") - .def( - "save", - [](const models::WordPiece& wordpiece, - const std::string& folder, - const py::object& py_obj) { - std::string prefix = ""; - if (!py_obj.is(py::none())) { - prefix = py_obj.cast(); - } - return wordpiece.Save(folder, prefix); - }, - py::arg("folder"), - py::arg("prefix") = py::none()); - py::class_(submodule, "FastWordPiece") - .def(py::init<>()) - .def(py::init(), - py::arg("vocab"), - py::arg("unk_token") = "[UNK]", - py::arg("max_input_chars_per_word") = 100, - py::arg("continuing_subword_prefix") = "##", - py::arg("with_pretokenization") = false) - .def("tokenize", &models::FastWordPiece::Tokenize) - .def("token_to_id", - [](const models::FastWordPiece& model, const std::string& token) { - uint32_t id; - model.TokenToId(token, &id); - return id; - }) - .def("id_to_token", - [](const models::FastWordPiece& model, uint32_t id) { - std::string token; - model.IdToToken(id, &token); - return token; - }) - .def("get_vocab", &models::FastWordPiece::GetVocab) - .def("get_vocab_size", &models::FastWordPiece::GetVocabSize) - .def_static("read_file", - &models::FastWordPiece::GetVocabFromFile, - py::arg("vocab")) - .def_static("from_file", - &models::FastWordPiece::GetFastWordPieceFromFile, - py::arg("vocab"), - py::arg("unk_token") = "[UNK]", - py::arg("max_input_chars_per_word") = 100, - py::arg("continuing_subword_prefix") = "##", - py::arg("with_pretokenization") = false) - .def( - "save", - [](const models::FastWordPiece& wordpiece, - const std::string& folder, - const py::object& py_obj) { - std::string prefix = ""; - if (!py_obj.is(py::none())) { - prefix = py_obj.cast(); - } - return wordpiece.Save(folder, prefix); - }, - py::arg("folder"), - py::arg("prefix") = py::none()); - py::class_(submodule, "BPE") - .def(py::init([](const py::object& py_vocab, - const py::object& py_merges, - const py::object& py_cache_capacity, - const py::object& py_dropout, - const py::object& py_unk_token, - const py::object& py_continuing_subword_prefix, - const py::object& py_end_of_word_suffix, - const py::object& py_fuse_unk) { - core::Vocab vocab; - if (!py_vocab.is(py::none())) { - vocab = py_vocab.cast(); - } - - core::Merges merges; - if (!py_merges.is(py::none())) { - merges = py_merges.cast(); - } - - size_t cache_capacity = utils::DEFAULT_CACHE_CAPACITY; - if (!py_cache_capacity.is(py::none())) { - cache_capacity = py_cache_capacity.cast(); - } - - std::vector dropout; - if (!py_dropout.is(py::none())) { - dropout.emplace_back(py_dropout.cast()); - } - - std::vector unk_token; - if (!py_unk_token.is(py::none())) { - unk_token.emplace_back(py_unk_token.cast()); - } - - std::vector continuing_subword_prefix; - if (!py_continuing_subword_prefix.is(py::none())) { - continuing_subword_prefix.emplace_back( - py_continuing_subword_prefix.cast()); - } - - std::vector end_of_word_suffix; - if (!py_end_of_word_suffix.is(py::none())) { - end_of_word_suffix.emplace_back( - py_end_of_word_suffix.cast()); - } - - bool fuse_unk = false; - if (!py_fuse_unk.is(py::none())) { - fuse_unk = py_fuse_unk.cast(); - } - models::BPE self(vocab, - merges, - cache_capacity, - dropout, - unk_token, - continuing_subword_prefix, - end_of_word_suffix, - fuse_unk); - return self; - }), - py::arg("vocab") = py::none(), - py::arg("merges") = py::none(), - py::arg("cache_capacity") = py::none(), - py::arg("dropout") = py::none(), - py::arg("unk_token") = py::none(), - py::arg("continuing_subword_prefix") = py::none(), - py::arg("end_of_word_suffix") = py::none(), - py::arg("fuse_unk") = py::none()) - .def("tokenize", &models::BPE::Tokenize) - .def("token_to_id", - [](const models::BPE& model, const std::string& token) { - uint32_t id; - model.TokenToId(token, &id); - return id; - }) - .def("id_to_token", - [](const models::BPE& model, uint32_t id) { - std::string token; - model.IdToToken(id, &token); - return token; - }) - .def("get_vocab", &models::BPE::GetVocab) - .def("get_vocab_size", &models::BPE::GetVocabSize) - .def( - "save", - [](const models::BPE& bpe, - const std::string& folder, - const py::object& py_obj) { - std::string prefix = ""; - if (!py_obj.is(py::none())) { - prefix = py_obj.cast(); - } - return bpe.Save(folder, prefix); - }, - py::arg("folder"), - py::arg("prefix") = py::none()) - .def_static( - "read_file", - [](const std::string& vocab_path, const std::string& merges_path) { - core::Vocab vocab; - core::Merges merges; - models::BPE::GetVocabAndMergesFromFile( - vocab_path, merges_path, &vocab, &merges); - return py::make_tuple(vocab, merges); - }, - py::arg("vocab"), - py::arg("merges")) - .def_static( - "from_file", - [](const std::string& vocab_path, - const std::string& merges_path, - const py::kwargs& kwargs) { - core::Vocab vocab; - core::Merges merges; - models::BPE::GetVocabAndMergesFromFile( - vocab_path, merges_path, &vocab, &merges); - VLOG(6) << "In BPE from_file:"; - size_t cache_capacity = utils::DEFAULT_CACHE_CAPACITY; - if (kwargs.contains("cache_capacity")) { - cache_capacity = kwargs["cache_capacity"].cast(); - VLOG(6) << "cache_capacity = " << cache_capacity; - } - std::vector dropout; - if (kwargs.contains("dropout")) { - dropout.emplace_back(kwargs["dropout"].cast()); - VLOG(6) << "dropout = " << kwargs["dropout"].cast(); - } - - std::vector unk_token; - if (kwargs.contains("unk_token")) { - unk_token.emplace_back(kwargs["unk_token"].cast()); - VLOG(6) << "unk_token = " - << kwargs["unk_token"].cast(); - } - - std::vector continuing_subword_prefix; - if (kwargs.contains("continuing_subword_prefix")) { - continuing_subword_prefix.emplace_back( - kwargs["continuing_subword_prefix"].cast()); - VLOG(6) - << "continuing_subword_prefix = " - << kwargs["continuing_subword_prefix"].cast(); - } - - std::vector end_of_word_suffix; - if (kwargs.contains("end_of_word_suffix")) { - end_of_word_suffix.emplace_back( - kwargs["end_of_word_suffix"].cast()); - VLOG(6) << "end_of_word_suffix = " - << kwargs["end_of_word_suffix"].cast(); - } - - bool fuse_unk = false; - if (kwargs.contains("fuse_unk")) { - fuse_unk = kwargs["fuse_unk"].cast(); - VLOG(6) << "fuse_unk = " << kwargs["fuse_unk"].cast(); - } - return models::BPE(vocab, - merges, - cache_capacity, - dropout, - unk_token, - continuing_subword_prefix, - end_of_word_suffix, - fuse_unk); - }, - py::arg("vocab"), - py::arg("merges")); - py::class_(submodule, "Unigram") - .def(py::init([](const py::object& py_vocab_list, - const py::object& py_unk_token_id) { - if (py_vocab_list.is(py::none()) && - py_unk_token_id.is(py::none())) { - return models::Unigram(); - } else if (!py_vocab_list.is(py::none()) && - !py_unk_token_id.is(py::none())) { - try { - core::VocabList vocab_list = - py_vocab_list.cast(); - size_t unk_id = py_unk_token_id.cast(); - return models::Unigram(vocab_list, {unk_id}); - } catch (std::exception& e) { - VLOG(0) << "Init Unigram error:" << e.what(); - goto error; - } - } - error: - throw py::value_error( - "`vocab` and `unk_id` must be both specified"); - }), - py::arg("vocab") = py::none(), - py::arg("unk_id") = py::none()) - .def("tokenize", &models::Unigram::Tokenize) - .def("token_to_id", - [](const models::Unigram& model, const std::string& token) { - uint32_t id; - model.TokenToId(token, &id); - return id; - }) - .def("id_to_token", - [](const models::Unigram& model, uint32_t id) { - std::string token; - model.IdToToken(id, &token); - return token; - }) - .def("get_vocab", &models::Unigram::GetVocab) - .def("get_vocab_size", &models::Unigram::GetVocabSize) - .def("set_filter_token", - &models::Unigram::SetFilterToken, - py::arg("filter_token") = "") - .def("set_split_rule", - &models::Unigram::SetSplitRule, - py::arg("split_rule") = "") - .def( - "save", - [](const models::Unigram& unigram, - const std::string& folder, - const py::object& py_obj) { - std::string prefix = ""; - if (!py_obj.is(py::none())) { - prefix = py_obj.cast(); - } - return unigram.Save(folder, prefix); - }, - py::arg("folder"), - py::arg("prefix") = py::none()); -} -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/models.h b/fast_tokenizer/fast_tokenizer/pybind/models.h deleted file mode 100644 index ca675e61c61e..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/models.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void BindModels(pybind11::module* m); - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/normalizers.cc b/fast_tokenizer/fast_tokenizer/pybind/normalizers.cc deleted file mode 100644 index 9c561b118503..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/normalizers.cc +++ /dev/null @@ -1,462 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/normalizers/normalizers.h" -#include -#include "fast_tokenizer/pybind/normalizers.h" - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -class PyNormalizer : public normalizers::Normalizer { -public: - using Normalizer::Normalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_PURE_NAME( - void, Normalizer, "__call__", operator(), mut_str); - } -}; - -class PyBertNormalizer : public normalizers::BertNormalizer { -public: - using BertNormalizer::BertNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, BertNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyReplaceNormalizer : public normalizers::ReplaceNormalizer { -public: - using ReplaceNormalizer::ReplaceNormalizer; - PyReplaceNormalizer(const ReplaceNormalizer& r) : ReplaceNormalizer(r) {} - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, ReplaceNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyStripNormalizer : public normalizers::StripNormalizer { -public: - using StripNormalizer::StripNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, StripNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyStripAccentsNormalizer : public normalizers::StripAccentsNormalizer { -public: - using StripAccentsNormalizer::StripAccentsNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, StripAccentsNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyNFCNormalizer : public normalizers::NFCNormalizer { -public: - using NFCNormalizer::NFCNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, NFCNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyNFKCNormalizer : public normalizers::NFKCNormalizer { -public: - using NFKCNormalizer::NFKCNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, NFKCNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyNFDNormalizer : public normalizers::NFDNormalizer { -public: - using NFDNormalizer::NFDNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, NFDNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyNFKDNormalizer : public normalizers::NFKDNormalizer { -public: - using NFKDNormalizer::NFKDNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, NFKDNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyNmtNormalizer : public normalizers::NmtNormalizer { -public: - using NmtNormalizer::NmtNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, NmtNormalizer, "__call__", operator(), mut_str); - } -}; - -class PySequenceNormalizer : public normalizers::SequenceNormalizer { -public: - using SequenceNormalizer::SequenceNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, SequenceNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyLowercaseNormalizer : public normalizers::LowercaseNormalizer { -public: - using LowercaseNormalizer::LowercaseNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, LowercaseNormalizer, "__call__", operator(), mut_str); - } -}; - -class PyPrecompiledNormalizer : public normalizers::PrecompiledNormalizer { -public: - using PrecompiledNormalizer::PrecompiledNormalizer; - virtual void operator()( - normalizers::NormalizedString* mut_str) const override { - PYBIND11_OVERLOAD_NAME( - void, PrecompiledNormalizer, "__call__", operator(), mut_str); - } -}; - -void BindNormalizers(pybind11::module* m) { - auto submodule = m->def_submodule("normalizers", "The normalizers module"); - py::class_(submodule, "NormalizedString") - .def(py::init()) - .def(py::init<>()) - .def("__str__", &normalizers::NormalizedString::GetStr); - py::class_(submodule, "Normalizer") - .def(py::init<>()) - .def("normalize_str", - [](const normalizers::Normalizer& self, const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::Normalizer::operator()); - py::class_(submodule, - "BertNormalizer") - .def(py::init(), - py::arg("clean_text") = true, - py::arg("handle_chinese_chars") = true, - py::arg("strip_accents") = true, - py::arg("lowercase") = true) - .def(py::init([](bool clean_text, - bool handle_chinese_chars, - const py::object& strip_accents_obj, - bool lowercase) { - bool strip_accents = lowercase; - if (!strip_accents_obj.is(py::none())) { - strip_accents = strip_accents_obj.cast(); - } - return std::unique_ptr( - new normalizers::BertNormalizer(clean_text, - handle_chinese_chars, - strip_accents, - lowercase)); - }), - py::arg("clean_text") = true, - py::arg("handle_chinese_chars") = true, - py::arg("strip_accents") = true, - py::arg("lowercase") = true) - .def("normalize_str", - [](const normalizers::BertNormalizer& self, const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::BertNormalizer::operator()) - .def("__getstate__", [](const normalizers::BertNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - - py::class_( - submodule, "ReplaceNormalizer") - .def(py::init(), - py::arg("replace_normalizer")) - .def(py::init(), - py::arg("pattern"), - py::arg("content")) - .def("normalize_str", - [](const normalizers::ReplaceNormalizer& self, - const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::ReplaceNormalizer::operator()) - .def("__getstate__", [](const normalizers::ReplaceNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - - py::class_(submodule, - "StripNormalizer") - .def(py::init(), - py::arg("left") = true, - py::arg("right") = true) - .def( - "normalize_str", - [](const normalizers::StripNormalizer& self, const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::StripNormalizer::operator()) - .def("__getstate__", [](const normalizers::StripNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_( - submodule, "StripAccentsNormalizer") - .def(py::init<>()) - .def("normalize_str", - [](const normalizers::StripAccentsNormalizer& self, - const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::StripAccentsNormalizer::operator()) - .def("__getstate__", [](const normalizers::StripAccentsNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_(submodule, - "NFCNormalizer") - .def(py::init<>()) - .def("normalize_str", - [](const normalizers::NFCNormalizer& self, const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::NFCNormalizer::operator()) - .def("__getstate__", [](const normalizers::NFCNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_(submodule, - "NFDNormalizer") - .def(py::init<>()) - .def("normalize_str", - [](const normalizers::NFDNormalizer& self, const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::NFDNormalizer::operator()) - .def("__getstate__", [](const normalizers::NFDNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_(submodule, - "NFKCNormalizer") - .def(py::init<>()) - .def("normalize_str", - [](const normalizers::NFKCNormalizer& self, const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::NFKCNormalizer::operator()) - .def("__getstate__", [](const normalizers::NFKCNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_(submodule, - "NFKDNormalizer") - .def(py::init<>()) - .def("normalize_str", - [](const normalizers::NFKDNormalizer& self, const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::NFKDNormalizer::operator()) - .def("__getstate__", [](const normalizers::NFKDNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_(submodule, - "NmtNormalizer") - .def(py::init<>()) - .def("normalize_str", - [](const normalizers::NmtNormalizer& self, const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::NmtNormalizer::operator()) - .def("__getstate__", [](const normalizers::NmtNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_( - submodule, "LowercaseNormalizer") - .def(py::init<>()) - .def("normalize_str", - [](const normalizers::LowercaseNormalizer& self, - const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::LowercaseNormalizer::operator()) - .def("__getstate__", [](const normalizers::LowercaseNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_( - submodule, "SequenceNormalizer") - .def( - py::init([](const py::list& py_list) { - normalizers::Normalizer* normalizer_ptr; - std::vector normalizers; - for (py::handle py_normalizer : py_list) { - if (pybind11::type::of(py_normalizer) - .is(py::type::of())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of< - normalizers::ReplaceNormalizer>())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of< - normalizers::SequenceNormalizer>())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of< - normalizers::StripAccentsNormalizer>())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of< - normalizers::StripNormalizer>())) { - normalizer_ptr = - py_normalizer.cast(); - } else if (pybind11::type::of(py_normalizer) - .is(py::type::of< - normalizers::PrecompiledNormalizer>())) { - normalizer_ptr = - py_normalizer.cast(); - } else { - throw py::value_error( - "Type of normalizers should be one of " - "`LowercaseNormalizer`," - " `BertNormalizer`, `NFCNormalizer`, `NFKCNormalizer`, " - "`NFDNormalizer`," - " `NFKDNormalizer`, `NmtNormalizer`, `ReplaceNormalizer`, " - "`SequenceNormalizer`," - " `StripAccentsNormalizer`, `StripNormalizer`, " - "`PrecompiledNormalizer`"); - } - normalizers.push_back(normalizer_ptr); - } - return normalizers::SequenceNormalizer(normalizers); - }), - py::arg("normalizers")) - .def("normalize_str", - [](const normalizers::SequenceNormalizer& self, - const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::SequenceNormalizer::operator()) - .def("__getstate__", [](const normalizers::SequenceNormalizer& self) { - nlohmann::json j = self; - return j.dump(); - }); - py::class_( - submodule, "PrecompiledNormalizer") - .def(py::init<>()) - .def(py::init(), py::arg("precompiled_charsmap")) - .def("normalize_str", - [](const normalizers::PrecompiledNormalizer& self, - const std::string& str) { - normalizers::NormalizedString normalized(str); - self(&normalized); - return normalized.GetStr(); - }, - py::arg("sequence")) - .def("__call__", &normalizers::PrecompiledNormalizer::operator()); -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/normalizers.h b/fast_tokenizer/fast_tokenizer/pybind/normalizers.h deleted file mode 100644 index 64cd9b6e2ed4..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/normalizers.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void BindNormalizers(pybind11::module* m); - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/postprocessors.cc b/fast_tokenizer/fast_tokenizer/pybind/postprocessors.cc deleted file mode 100644 index 6795c125481a..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/postprocessors.cc +++ /dev/null @@ -1,418 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/postprocessors/postprocessors.h" -#include -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/pybind/postprocessors.h" -#include "fast_tokenizer/pybind/utils.h" -#include "glog/logging.h" - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -class PyPostProcessor : public postprocessors::PostProcessor { -public: - using PostProcessor::PostProcessor; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override { - PYBIND11_OVERLOAD_PURE_NAME(void, - PostProcessor, - "__call__", - operator(), - encoding, - pair_encoding, - add_special_tokens, - result_encoding); - } - virtual size_t AddedTokensNum(bool is_pair) const override { - PYBIND11_OVERLOAD_PURE_NAME(size_t, - PostProcessor, - "num_special_tokens_to_add", - AddedTokensNum, - is_pair); - } -}; - -class PyBertPostProcessor : public postprocessors::BertPostProcessor { -public: - using BertPostProcessor::BertPostProcessor; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override { - PYBIND11_OVERLOAD_NAME(void, - BertPostProcessor, - "__call__", - operator(), - encoding, - pair_encoding, - add_special_tokens, - result_encoding); - } - virtual size_t AddedTokensNum(bool is_pair) const override { - PYBIND11_OVERLOAD_NAME(size_t, - BertPostProcessor, - "num_special_tokens_to_add", - AddedTokensNum, - is_pair); - } -}; - -class PyTemplatePostProcessor : public postprocessors::TemplatePostProcessor { -public: - using TemplatePostProcessor::TemplatePostProcessor; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override { - PYBIND11_OVERLOAD_NAME(void, - TemplatePostProcessor, - "__call__", - operator(), - encoding, - pair_encoding, - add_special_tokens, - result_encoding); - } - virtual size_t AddedTokensNum(bool is_pair) const override { - PYBIND11_OVERLOAD_NAME(size_t, - TemplatePostProcessor, - "num_special_tokens_to_add", - AddedTokensNum, - is_pair); - } -}; - -class PyRobertaPostProcessor : public postprocessors::RobertaPostProcessor { -public: - using RobertaPostProcessor::RobertaPostProcessor; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override { - PYBIND11_OVERLOAD_NAME(void, - RobertaPostProcessor, - "__call__", - operator(), - encoding, - pair_encoding, - add_special_tokens, - result_encoding); - } - virtual size_t AddedTokensNum(bool is_pair) const override { - PYBIND11_OVERLOAD_NAME(size_t, - RobertaPostProcessor, - "num_special_tokens_to_add", - AddedTokensNum, - is_pair); - } -}; - -class PyByteLevelPostProcessor : public postprocessors::ByteLevelPostProcessor { -public: - using ByteLevelPostProcessor::ByteLevelPostProcessor; - virtual void operator()(core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens, - core::Encoding* result_encoding) const override { - PYBIND11_OVERLOAD_NAME(void, - ByteLevelPostProcessor, - "__call__", - operator(), - encoding, - pair_encoding, - add_special_tokens, - result_encoding); - } - virtual size_t AddedTokensNum(bool is_pair) const override { - PYBIND11_OVERLOAD_NAME(size_t, - ByteLevelPostProcessor, - "num_special_tokens_to_add", - AddedTokensNum, - is_pair); - } -}; - -void BindPostProcessors(pybind11::module* m) { - auto submodule = - m->def_submodule("postprocessors", "The postprocessors module"); - py::class_(submodule, - "PostProcessor") - .def(py::init<>()) - .def("num_special_tokens_to_add", - &postprocessors::PostProcessor::AddedTokensNum, - py::arg("is_pair")) - .def("__call__", - [](const postprocessors::PostProcessor& self, - core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens) { - core::Encoding result_encoding; - self( - encoding, pair_encoding, add_special_tokens, &result_encoding); - return result_encoding; - }, - py::arg("encoding"), - py::arg("pair_encoding"), - py::arg("add_special_tokens")); - py::class_( - submodule, "BertPostProcessor") - .def(py::init<>()) - .def(py::init&, - const std::pair&>(), - py::arg("sep"), - py::arg("cls")) - .def("num_special_tokens_to_add", - &postprocessors::BertPostProcessor::AddedTokensNum, - py::arg("is_pair")) - .def("__call__", - [](const postprocessors::BertPostProcessor& self, - core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens) { - core::Encoding result_encoding; - self( - encoding, pair_encoding, add_special_tokens, &result_encoding); - return result_encoding; - }, - py::arg("encoding"), - py::arg("pair_encoding"), - py::arg("add_special_tokens")); - - // For Template Processing - py::class_(submodule, "SpecialToken") - .def(py::init<>()) - .def(py::init&, - const std::vector&>(), - py::arg("id"), - py::arg("ids"), - py::arg("tokens")) - .def(py::init(), - py::arg("token"), - py::arg("id")); - - py::class_(submodule, "Template") - .def(py::init<>()) - .def(py::init(), py::arg("template")) - .def(py::init&>(), py::arg("pieces")) - .def(py::init&>(), - py::arg("pieces")); - - py::class_( - submodule, "TemplatePostProcessor") - .def( - py::init([](const py::object& single_obj, - const py::object& pair_obj, - const py::object& special_tokens_obj) { - postprocessors::TemplatePostProcessor self; - // Setting single - if (py::isinstance(single_obj)) { - std::vector template_piece = - CastPyArg2VectorOfStr(single_obj.ptr(), 0); - self.UpdateSinglePieces(template_piece); - } else if (py::isinstance(single_obj)) { - self.UpdateSinglePieces( - CastPyArg2AttrString(single_obj.ptr(), 0)); - } else { - throw py::value_error( - "Type of args single need to be List[str] or str."); - } - // Setting pair - if (py::isinstance(pair_obj)) { - std::vector template_piece = - CastPyArg2VectorOfStr(pair_obj.ptr(), 0); - self.UpdatePairPieces(template_piece); - } else if (py::isinstance(pair_obj)) { - self.UpdatePairPieces(CastPyArg2AttrString(pair_obj.ptr(), 0)); - } else { - throw py::value_error( - "Type of args pair need to be List[str] or str."); - } - // Setting special_tokens - if (py::isinstance(special_tokens_obj)) { - std::vector special_tokens; - for (auto& str : special_tokens_obj.cast()) { - if (py::isinstance(str)) { - auto token_tuple = str.cast(); - uint32_t id; - std::string token; - if (token_tuple.size() == 2) { - if (py::isinstance(token_tuple[0]) && - py::isinstance(token_tuple[1])) { - token = token_tuple[0].cast(); - id = token_tuple[1].cast(); - } else if (py::isinstance(token_tuple[1]) && - py::isinstance(token_tuple[0])) { - token = token_tuple[1].cast(); - id = token_tuple[0].cast(); - } else { - throw py::value_error( - "`Tuple` with both a token and its associated ID, in " - "any order"); - } - special_tokens.emplace_back(token, id); - } else { - throw py::value_error( - "Type of args special_tokens need to be " - "List[Union[Tuple[int, str], Tuple[str, int], dict]]"); - } - } else if (py::isinstance(str)) { - auto token_dict = str.cast(); - std::string id; - std::vector ids; - std::vector tokens; - if (token_dict.contains("id") && - py::isinstance(token_dict["id"])) { - id = token_dict["id"].cast(); - } else { - throw py::value_error( - "Type of args special_tokens dict need to have key 'id'" - "and the respective value should be `str`"); - } - if (token_dict.contains("ids") && - py::isinstance(token_dict["ids"])) { - for (auto py_id : token_dict["ids"].cast()) { - if (py::isinstance(py_id)) { - ids.push_back(py_id.cast()); - } else { - throw py::value_error( - "Type of args special_tokens dict need to have key " - "'ids'" - "and the respective value should be List[int]"); - } - } - } else { - throw py::value_error( - "Type of args special_tokens dict need to have key " - "'ids'" - "and the respective value should be List[int]"); - } - if (token_dict.contains("tokens") && - py::isinstance(token_dict["tokens"])) { - for (auto& py_token : - token_dict["tokens"].cast()) { - if (py::isinstance(py_token)) { - tokens.push_back(py_token.cast()); - } else { - throw py::value_error( - "Type of args special_tokens dict need to have key " - "'tokens'" - "and the respective value should be List[str]"); - } - } - } else { - throw py::value_error( - "Type of args special_tokens dict need to have key " - "'tokens'" - "and the respective value should be List[str]"); - } - special_tokens.emplace_back(id, ids, tokens); - } else { - throw py::value_error( - "Type of args special_tokens need to be " - "List[Union[Tuple[int, str], Tuple[str, int], dict]]"); - } - } - self.SetTokensMap(special_tokens); - } else { - throw py::value_error( - "Type of args special_tokens need to be " - "List[Union[Tuple[int, str], Tuple[str, int], dict]]"); - } - return self; - }), - py::arg("single"), - py::arg("pair"), - py::arg("special_tokens")) - .def("num_special_tokens_to_add", - &postprocessors::TemplatePostProcessor::AddedTokensNum, - py::arg("is_pair")) - .def("__call__", - [](const postprocessors::TemplatePostProcessor& self, - core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens) { - core::Encoding result_encoding; - self( - encoding, pair_encoding, add_special_tokens, &result_encoding); - return result_encoding; - }, - py::arg("encoding"), - py::arg("pair_encoding"), - py::arg("add_special_tokens")); - - py::class_( - submodule, "RobertaPostProcessor") - .def(py::init<>()) - .def(py::init&, - const std::pair&, - bool, - bool>(), - py::arg("sep"), - py::arg("cls"), - py::arg("trim_offsets") = true, - py::arg("add_prefix_space") = true) - .def("num_special_tokens_to_add", - &postprocessors::RobertaPostProcessor::AddedTokensNum, - py::arg("is_pair")) - .def("__call__", - [](const postprocessors::RobertaPostProcessor& self, - core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens) { - core::Encoding result_encoding; - self( - encoding, pair_encoding, add_special_tokens, &result_encoding); - return result_encoding; - }, - py::arg("encoding"), - py::arg("pair_encoding"), - py::arg("add_special_tokens")); - py::class_( - submodule, "ByteLevelPostProcessor") - .def(py::init(), - py::arg("add_prefix_space") = true, - py::arg("trim_offsets") = true, - py::arg("use_regex") = true) - .def("num_special_tokens_to_add", - &postprocessors::ByteLevelPostProcessor::AddedTokensNum, - py::arg("is_pair")) - .def("__call__", - [](const postprocessors::ByteLevelPostProcessor& self, - core::Encoding* encoding, - core::Encoding* pair_encoding, - bool add_special_tokens) { - core::Encoding result_encoding; - self( - encoding, pair_encoding, add_special_tokens, &result_encoding); - return result_encoding; - }, - py::arg("encoding"), - py::arg("pair_encoding"), - py::arg("add_special_tokens")); - -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/postprocessors.h b/fast_tokenizer/fast_tokenizer/pybind/postprocessors.h deleted file mode 100644 index b30b31a951ee..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/postprocessors.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void BindPostProcessors(pybind11::module* m); - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/pretokenizers.cc b/fast_tokenizer/fast_tokenizer/pybind/pretokenizers.cc deleted file mode 100644 index efb9ae77446d..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/pretokenizers.cc +++ /dev/null @@ -1,265 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - - -#include "fast_tokenizer/pretokenizers/pretokenizers.h" - -#include - -#include "fast_tokenizer/pybind/pretokenizers.h" -#include "re2/re2.h" - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -class PyPreTokenizer : public pretokenizers::PreTokenizer { -public: - using PreTokenizer::PreTokenizer; - virtual void operator()( - pretokenizers::PreTokenizedString* pretokenized) const override { - PYBIND11_OVERLOAD_PURE_NAME( - void, PreTokenizer, "__call__", operator(), pretokenized); - } -}; - -class PyWhitespacePreTokenizer : public pretokenizers::WhitespacePreTokenizer { -public: - using WhitespacePreTokenizer::WhitespacePreTokenizer; - virtual void operator()( - pretokenizers::PreTokenizedString* pretokenized) const override { - PYBIND11_OVERLOAD_NAME( - void, WhitespacePreTokenizer, "__call__", operator(), pretokenized); - } -}; - -class PyWhitespaceAndPunctuationPreTokenizer - : public pretokenizers::WhitespaceAndPunctuationPreTokenizer { -public: - using WhitespaceAndPunctuationPreTokenizer:: - WhitespaceAndPunctuationPreTokenizer; - virtual void operator()( - pretokenizers::PreTokenizedString* pretokenized) const override { - PYBIND11_OVERLOAD_NAME(void, - WhitespaceAndPunctuationPreTokenizer, - "__call__", - operator(), - pretokenized); - } -}; - -class PyBertPreTokenizer : public pretokenizers::BertPreTokenizer { -public: - using BertPreTokenizer::BertPreTokenizer; - virtual void operator()( - pretokenizers::PreTokenizedString* pretokenized) const override { - PYBIND11_OVERLOAD_NAME( - void, BertPreTokenizer, "__call__", operator(), pretokenized); - } -}; - -class PyMetaSpacePreTokenizer : public pretokenizers::MetaSpacePreTokenizer { -public: - using MetaSpacePreTokenizer::MetaSpacePreTokenizer; - virtual void operator()( - pretokenizers::PreTokenizedString* pretokenized) const override { - PYBIND11_OVERLOAD_NAME( - void, MetaSpacePreTokenizer, "__call__", operator(), pretokenized); - } -}; - -class PySequencePreTokenizer : public pretokenizers::SequencePreTokenizer { -public: - using SequencePreTokenizer::SequencePreTokenizer; - virtual void operator()( - pretokenizers::PreTokenizedString* pretokenized) const override { - PYBIND11_OVERLOAD_NAME( - void, SequencePreTokenizer, "__call__", operator(), pretokenized); - } -}; - -class PyByteLevelPreTokenizer : public pretokenizers::ByteLevelPreTokenizer { -public: - using ByteLevelPreTokenizer::ByteLevelPreTokenizer; - virtual void operator()( - pretokenizers::PreTokenizedString* pretokenized) const override { - PYBIND11_OVERLOAD_NAME( - void, ByteLevelPreTokenizer, "__call__", operator(), pretokenized); - } -}; - -class PySplitPreTokenizer : public pretokenizers::SplitPreTokenizer { -public: - using SplitPreTokenizer::SplitPreTokenizer; - virtual void operator()( - pretokenizers::PreTokenizedString* pretokenized) const override { - PYBIND11_OVERLOAD_NAME( - void, SplitPreTokenizer, "__call__", operator(), pretokenized); - } -}; - -void BindPreTokenizers(pybind11::module* m) { - auto sub_module = - m->def_submodule("pretokenizers", "The pretokenizers module"); - py::class_(sub_module, "StringSplit") - .def(py::init(), - py::arg("nomalized_text")) - .def(py::init&>(), - py::arg("nomalized_text"), - py::arg("tokens")) - .def_readwrite("normalized", &pretokenizers::StringSplit::normalized_) - .def_readwrite("tokens", &pretokenizers::StringSplit::tokens_); - py::class_(sub_module, - "PreTokenizedString") - .def(py::init<>()) - .def(py::init(), py::arg("raw_text")) - .def(py::init(), - py::arg("nomalized_text")) - .def("get_string_split", &pretokenizers::PreTokenizedString::GetSplit) - .def("get_string_splits_size", - &pretokenizers::PreTokenizedString::GetSplitsSize) - .def("get_original_text", - &pretokenizers::PreTokenizedString::GetOriginStr) - .def( - "get_splits", - [](const pretokenizers::PreTokenizedString& self, - const std::string& offset_referential, - const std::string& offset_type) { - bool is_original = true; - if (offset_referential != "original") { - is_original = false; - } - core::OffsetType type = core::OffsetType::CHAR; - if (offset_type != "char") { - type = core::OffsetType::BYTE; - } - return self.GetSplits(is_original, type); - }, - py::arg("offset_referential") = "original", - py::arg("offset_type") = "char") - .def("to_encoding", - [](const pretokenizers::PreTokenizedString& self, - const std::vector& word_idx, - uint32_t type_id, - core::OffsetType offset_type) { - core::Encoding encoding; - self.TransformToEncoding( - word_idx, type_id, offset_type, &encoding); - return encoding; - }); - py::class_(sub_module, - "PreTokenizer") - .def(py::init<>()) - .def("__call__", &pretokenizers::PreTokenizer::operator()); - py::class_( - sub_module, "WhitespacePreTokenizer") - .def(py::init<>()) - .def("__call__", &pretokenizers::WhitespacePreTokenizer::operator()); - py::class_( - sub_module, "WhitespaceAndPunctuationPreTokenizer") - .def(py::init<>()) - .def("__call__", - &pretokenizers::WhitespaceAndPunctuationPreTokenizer::operator()); - py::class_( - sub_module, "BertPreTokenizer") - .def(py::init<>()) - .def("__call__", &pretokenizers::BertPreTokenizer::operator()); - py::class_( - sub_module, "MetaSpacePreTokenizer") - .def(py::init(), - py::arg("replacement") = "_", - py::arg("add_prefix_space") = true) - .def("__call__", &pretokenizers::MetaSpacePreTokenizer::operator()); - py::class_( - sub_module, "SequencePreTokenizer") - .def( - py::init([](const py::list& py_list) { - pretokenizers::PreTokenizer* pretokenizer_ptr; - std::vector pretokenizers; - for (py::handle py_pretokenizer : py_list) { - if (pybind11::type::of(py_pretokenizer) - .is(py::type::of())) { - pretokenizer_ptr = - py_pretokenizer.cast(); - } else if (pybind11::type::of(py_pretokenizer) - .is(py::type::of< - pretokenizers::MetaSpacePreTokenizer>())) { - pretokenizer_ptr = - py_pretokenizer - .cast(); - } else if (pybind11::type::of(py_pretokenizer) - .is(py::type::of< - pretokenizers::SequencePreTokenizer>())) { - pretokenizer_ptr = - py_pretokenizer - .cast(); - } else if (pybind11::type::of(py_pretokenizer) - .is(py::type::of< - pretokenizers::WhitespacePreTokenizer>())) { - pretokenizer_ptr = - py_pretokenizer - .cast(); - } else if (pybind11::type::of(py_pretokenizer) - .is(py::type::of< - pretokenizers:: - WhitespaceAndPunctuationPreTokenizer>())) { - pretokenizer_ptr = py_pretokenizer.cast< - pretokenizers::WhitespaceAndPunctuationPreTokenizer*>(); - } else if (pybind11::type::of(py_pretokenizer) - .is(py::type::of< - pretokenizers::ByteLevelPreTokenizer>())) { - pretokenizer_ptr = - py_pretokenizer - .cast(); - } else if (py::type::of(py_pretokenizer) - .is(py::type::of< - pretokenizers::SplitPreTokenizer>())) { - pretokenizer_ptr = - py_pretokenizer.cast(); - } else { - throw py::value_error( - "Type of normalizers should be one of `BertPreTokenizer`," - " `MetaSpacePreTokenizer`, `SequencePreTokenizer`," - " `WhitespacePreTokenizer`, `ByteLevelPreTokenizer`," - " `WhitespaceAndPunctuationPreTokenizer`, " - "`SplitPreTokenizer`"); - } - pretokenizers.push_back(pretokenizer_ptr); - } - return pretokenizers::SequencePreTokenizer(pretokenizers); - }), - py::arg("pretokenizers")) - .def("__call__", &pretokenizers::SequencePreTokenizer::operator()); - py::class_( - sub_module, "ByteLevelPreTokenizer") - .def(py::init(), - py::arg("add_prefix_space") = true, - py::arg("use_regex") = true) - .def("__call__", &pretokenizers::ByteLevelPreTokenizer::operator()); - py::class_( - sub_module, "SplitPreTokenizer") - .def(py::init(), - py::arg("pattern"), - py::arg("split_mode"), - py::arg("invert")) - .def("__call__", &pretokenizers::SplitPreTokenizer::operator()); -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/pretokenizers.h b/fast_tokenizer/fast_tokenizer/pybind/pretokenizers.h deleted file mode 100644 index ffe7b1adf83d..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/pretokenizers.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void BindPreTokenizers(pybind11::module* m); - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/pybind.cc b/fast_tokenizer/fast_tokenizer/pybind/pybind.cc deleted file mode 100644 index 3f7b32f56d88..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/pybind.cc +++ /dev/null @@ -1,51 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ -#include - -#include -#include - -#include "fast_tokenizer/pybind/core.h" -#include "fast_tokenizer/pybind/decoders.h" -#include "fast_tokenizer/pybind/models.h" -#include "fast_tokenizer/pybind/normalizers.h" -#include "fast_tokenizer/pybind/postprocessors.h" -#include "fast_tokenizer/pybind/pretokenizers.h" -#include "fast_tokenizer/pybind/tokenizers.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -PYBIND11_MODULE(core_tokenizers, m) { - m.doc() = "The paddlenlp fast_tokenizer core module."; - // 1. Bind normalizers submodule - BindNormalizers(&m); - // 2. Bind pre_tokenizers submodule - BindPreTokenizers(&m); - // 3. Bind models submodule - BindModels(&m); - // 4. Bind processors submodule - BindPostProcessors(&m); - // 5. Bind tokenizers submodule - BindTokenizers(&m); - // 6. Bind core - BindCore(&m); - // 7. Bind decoder submodule - BindDecoders(&m); -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/tokenizers.cc b/fast_tokenizer/fast_tokenizer/pybind/tokenizers.cc deleted file mode 100644 index 69902251a2ce..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/tokenizers.cc +++ /dev/null @@ -1,1428 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/pybind/tokenizers.h" - -#include - -#include - -#include "fast_tokenizer/core/tokenizer.h" -#include "fast_tokenizer/decoders/decoders.h" -#include "fast_tokenizer/models/models.h" -#include "fast_tokenizer/normalizers/normalizers.h" -#include "fast_tokenizer/postprocessors/postprocessors.h" -#include "fast_tokenizer/pretokenizers/pretokenizers.h" -#include "fast_tokenizer/pybind/exception.h" -#include "fast_tokenizer/pybind/utils.h" -#include "glog/logging.h" - -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -PyTypeObject* p_tokenizer_type; // For Tokenizer - -PyNumberMethods number_methods; -PySequenceMethods sequence_methods; -PyMappingMethods mapping_methods; - -typedef struct { - PyObject_HEAD core::Tokenizer tokenizer; - // Weak references - PyObject* weakrefs; -} TokenizerObject; - -static PyObject* TokenizerPropertiesGetNormaizer(TokenizerObject* self, - void* closure) { - py::object py_obj = py::cast(self->tokenizer.GetNormalizerPtr()); - py_obj.inc_ref(); - return py_obj.ptr(); -} - -static int TokenizerPropertiesSetNormalizer(TokenizerObject* self, - PyObject* value, - void* closure) { - TOKENIZERS_TRY - py::handle py_obj(value); - int ret = 0; - if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = - py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = - py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = - py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = - py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& normalizer = - py_obj.cast(); - self->tokenizer.SetNormalizer(normalizer); - } else if (py_obj.is(py::none())) { - self->tokenizer.ReleaseNormaizer(); - } else { - ret = 1; - throw std::runtime_error("Need to assign the object of Normalizer"); - } - return ret; - TOKENIZERS_CATCH_AND_THROW_RETURN_NEG -} - -static PyObject* TokenizerPropertiesGetPreTokenizer(TokenizerObject* self, - void* closure) { - py::object py_obj = py::cast(self->tokenizer.GetPreTokenizer()); - py_obj.inc_ref(); - return py_obj.ptr(); -} - -static int TokenizerPropertiesSetPreTokenizer(TokenizerObject* self, - PyObject* value, - void* closure) { - TOKENIZERS_TRY - py::handle py_obj(value); - int ret = 0; - if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& pretokenizer = - py_obj.cast(); - self->tokenizer.SetPreTokenizer(pretokenizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& pretokenizer = - py_obj.cast(); - self->tokenizer.SetPreTokenizer(pretokenizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of< - pretokenizers::WhitespaceAndPunctuationPreTokenizer>())) { - const auto& pretokenizer = - py_obj - .cast(); - self->tokenizer.SetPreTokenizer(pretokenizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& pretokenizer = - py_obj.cast(); - self->tokenizer.SetPreTokenizer(pretokenizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& pretokenizer = - py_obj.cast(); - self->tokenizer.SetPreTokenizer(pretokenizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& pretokenizer = - py_obj.cast(); - self->tokenizer.SetPreTokenizer(pretokenizer); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& pretokenizer = - py_obj.cast(); - self->tokenizer.SetPreTokenizer(pretokenizer); - } else if (py_obj.is(py::none())) { - self->tokenizer.ReleasePreTokenizer(); - } else { - ret = 1; - throw std::runtime_error("Need to assign the object of PreTokenizer"); - } - return ret; - TOKENIZERS_CATCH_AND_THROW_RETURN_NEG -} - -static PyObject* TokenizerPropertiesGetModel(TokenizerObject* self, - void* closure) { - py::object py_obj = py::cast(self->tokenizer.GetModelPtr()); - py_obj.inc_ref(); - return py_obj.ptr(); -} - -static int TokenizerPropertiesSetModel(TokenizerObject* self, - PyObject* value, - void* closure) { - TOKENIZERS_TRY - py::handle py_obj(value); - int ret = 0; - if (pybind11::type::of(py_obj).is(py::type::of())) { - const auto& model = py_obj.cast(); - self->tokenizer.SetModel(model); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& model = py_obj.cast(); - self->tokenizer.SetModel(model); - } else if (pybind11::type::of(py_obj).is(py::type::of())) { - const auto& model = py_obj.cast(); - self->tokenizer.SetModel(model); - } else if (pybind11::type::of(py_obj).is(py::type::of())) { - const auto& model = py_obj.cast(); - self->tokenizer.SetModel(model); - } else { - ret = 1; - throw std::runtime_error("Need to assign the object of Model"); - } - return ret; - TOKENIZERS_CATCH_AND_THROW_RETURN_NEG -} - -static PyObject* TokenizerPropertiesGetPostProcessor(TokenizerObject* self, - void* closure) { - py::object py_obj = py::cast(self->tokenizer.GetPostProcessorPtr()); - py_obj.inc_ref(); - return py_obj.ptr(); -} - -static int TokenizerPropertiesSetPostProcessor(TokenizerObject* self, - PyObject* value, - void* closure) { - TOKENIZERS_TRY - py::handle py_obj(value); - int ret = 0; - if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& processor = - py_obj.cast(); - self->tokenizer.SetPostProcessor(processor); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& processor = - py_obj.cast(); - self->tokenizer.SetPostProcessor(processor); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& processor = - py_obj.cast(); - self->tokenizer.SetPostProcessor(processor); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& processor = - py_obj.cast(); - self->tokenizer.SetPostProcessor(processor); - } else if (py_obj.is(py::none())) { - self->tokenizer.ReleasePostProcessor(); - } else { - ret = 1; - throw std::runtime_error("Need to assign the object of PostProcessor"); - } - return ret; - TOKENIZERS_CATCH_AND_THROW_RETURN_NEG -} - -static PyObject* TokenizerPropertiesGetPadding(TokenizerObject* self, - void* closure) { - if (!self->tokenizer.GetUsePadding()) { - Py_RETURN_NONE; - } - auto pad_method = self->tokenizer.GetPadMethod(); - PyObject* py_dict = PyDict_New(); - PyDict_SetItem(py_dict, ToPyObject("pad_id"), ToPyObject(pad_method.pad_id_)); - PyDict_SetItem(py_dict, - ToPyObject("pad_token_type_id"), - ToPyObject(pad_method.pad_token_type_id_)); - PyDict_SetItem( - py_dict, ToPyObject("pad_token"), ToPyObject(pad_method.pad_token_)); - if (pad_method.pad_to_multiple_of_ > 0) { - PyDict_SetItem(py_dict, - ToPyObject("pad_to_multiple_of"), - ToPyObject(pad_method.pad_to_multiple_of_)); - } else { - Py_INCREF(Py_None); - PyDict_SetItem(py_dict, ToPyObject("pad_to_multiple_of"), Py_None); - } - - PyDict_SetItem( - py_dict, - ToPyObject("direction"), - ToPyObject((pad_method.direction_ == core::Direction::RIGHT) ? "right" - : "left")); - if (pad_method.strategy_ == core::PadStrategy::BATCH_LONGEST) { - Py_INCREF(Py_None); - PyDict_SetItem(py_dict, ToPyObject("length"), Py_None); - } else { - PyDict_SetItem( - py_dict, ToPyObject("length"), ToPyObject(pad_method.pad_len_)); - } - return py_dict; -} - -static PyObject* TokenizerPropertiesGetTruncation(TokenizerObject* self, - void* closure) { - if (!self->tokenizer.GetUseTruncation()) { - Py_RETURN_NONE; - } - auto trunc_method = self->tokenizer.GetTruncMethod(); - PyObject* py_dict = PyDict_New(); - PyDict_SetItem( - py_dict, ToPyObject("max_length"), ToPyObject(trunc_method.max_len_)); - PyDict_SetItem( - py_dict, ToPyObject("stride"), ToPyObject(trunc_method.stride_)); - PyDict_SetItem( - py_dict, - ToPyObject("direction"), - ToPyObject((trunc_method.direction_ == core::Direction::RIGHT) ? "right" - : "left")); - if (trunc_method.strategy_ == core::TruncStrategy::LONGEST_FIRST) { - PyDict_SetItem( - py_dict, ToPyObject("strategy"), ToPyObject("longest_first")); - } else if (trunc_method.strategy_ == core::TruncStrategy::ONLY_FIRST) { - PyDict_SetItem(py_dict, ToPyObject("strategy"), ToPyObject("only_first")); - } else if (trunc_method.strategy_ == core::TruncStrategy::ONLY_SECOND) { - PyDict_SetItem(py_dict, ToPyObject("strategy"), ToPyObject("only_second")); - } - return py_dict; -} - -static PyObject* TokenizerPropertiesGetDecoder(TokenizerObject* self, - void* closure) { - py::object py_obj = py::cast(self->tokenizer.GetDecoderPtr()); - py_obj.inc_ref(); - return py_obj.ptr(); -} - -static int TokenizerPropertiesSetDecoder(TokenizerObject* self, - PyObject* value, - void* closure) { - TOKENIZERS_TRY - py::handle py_obj(value); - int ret = 0; - if (pybind11::type::of(py_obj).is(py::type::of())) { - const auto& decoder = py_obj.cast(); - self->tokenizer.SetDecoder(decoder); - } else if (py_obj.is(py::none())) { - self->tokenizer.ReleaseDecoder(); - } else { - ret = 1; - throw std::runtime_error("Need to assign the object of Decoder"); - } - return ret; - TOKENIZERS_CATCH_AND_THROW_RETURN_NEG -} - -struct PyGetSetDef tokenizer_variable_properties[] = { - {"normalizer", - (getter)TokenizerPropertiesGetNormaizer, - (setter)TokenizerPropertiesSetNormalizer, - nullptr, - nullptr}, - {"pretokenizer", - (getter)TokenizerPropertiesGetPreTokenizer, - (setter)TokenizerPropertiesSetPreTokenizer, - nullptr, - nullptr}, - {"model", - (getter)TokenizerPropertiesGetModel, - (setter)TokenizerPropertiesSetModel, - nullptr, - nullptr}, - {"postprocessor", - (getter)TokenizerPropertiesGetPostProcessor, - (setter)TokenizerPropertiesSetPostProcessor, - nullptr, - nullptr}, - {"padding", - (getter)TokenizerPropertiesGetPadding, - nullptr, - nullptr, - nullptr}, - {"truncation", - (getter)TokenizerPropertiesGetTruncation, - nullptr, - nullptr, - nullptr}, - {"decoder", - (getter)TokenizerPropertiesGetDecoder, - (setter)TokenizerPropertiesSetDecoder, - nullptr, - nullptr}, - {nullptr, nullptr, nullptr, nullptr, nullptr}}; - -PyObject* TokenizerNew(PyTypeObject* type, PyObject* args, PyObject* kwargs) { - PyObject* obj = type->tp_alloc(type, 0); - if (obj) { - auto v = reinterpret_cast(obj); - new (&(v->tokenizer)) core::Tokenizer(); - } - return obj; -} - -static void TokenizerDealloc(TokenizerObject* self) { - if (self->weakrefs != NULL) - PyObject_ClearWeakRefs(reinterpret_cast(self)); - self->tokenizer.~Tokenizer(); - Py_TYPE(self)->tp_free(reinterpret_cast(self)); -} - -int TokenizerInit(PyObject* self, PyObject* args, PyObject* kwargs) { - bool flag_kwargs = false; - if (kwargs) flag_kwargs = true; - // all kwargs - PyObject* kw_model = NULL; - // the keywords argument - static char* kwlist[] = {const_cast("model"), NULL}; - // 'O' Store a Python object (without any conversion) in a C object pointer, - // '|' Indicates that the remaining arguments in the Python argument list are - // optional. - bool flag_ = - PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &kw_model); - std::unordered_map kws_map{{"model", kw_model}}; - - auto py_tokenizer_ptr = reinterpret_cast(self); - Py_ssize_t args_num = PyTuple_Size(args); - - if (args_num == 1) { - py::object py_obj = - py::reinterpret_borrow(PyTuple_GET_ITEM(args, 0)); - if (pybind11::type::of(py_obj).is(py::type::of())) { - const auto& model = py_obj.cast(); - py_tokenizer_ptr->tokenizer.SetModel(model); - } else if (pybind11::type::of(py_obj).is( - py::type::of())) { - const auto& model = py_obj.cast(); - py_tokenizer_ptr->tokenizer.SetModel(model); - } else if (pybind11::type::of(py_obj).is(py::type::of())) { - const auto& model = py_obj.cast(); - py_tokenizer_ptr->tokenizer.SetModel(model); - } else if (pybind11::type::of(py_obj).is(py::type::of())) { - const auto& model = py_obj.cast(); - py_tokenizer_ptr->tokenizer.SetModel(model); - } else { - std::ostringstream oss; - oss << "Expected type of arguments is `model`"; - throw std::runtime_error(oss.str()); - } - return 0; - } else if (args_num >= 1) { - std::ostringstream oss; - oss << "Expected number of arguments is 0 or 1, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - return 1; -} - -// def add_special_tokens(token) -static PyObject* AddSpecialTokens(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_special_tokens = NULL; - static char* kwlist[] = {const_cast("tokens"), NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords( - args, kwargs, "|O", kwlist, &kw_special_tokens); - Py_ssize_t args_num = PyTuple_Size(args); - std::string tokens; - if (args_num == (Py_ssize_t)1) { - if (PyList_Check(kw_special_tokens)) { - std::vector added_tokens; - Py_ssize_t tokens_num = PyList_GET_SIZE(kw_special_tokens); - for (Py_ssize_t i = 0; i < tokens_num; ++i) { - PyObject* obj = PyList_GetItem(kw_special_tokens, i); - if (PyUnicode_Check(obj)) { - added_tokens.push_back( - core::AddedToken(CastPyArg2AttrString(obj, 0), true)); - } else { - py::handle py_obj(obj); - if (!py::type::of(py_obj).is(py::type::of())) { - throw std::runtime_error( - "The argument of tokens should be List[Union[str, " - "AddedToken]]"); - } - auto added_token = py_obj.cast(); - added_tokens.push_back(added_token); - } - } - return ToPyObject(self->tokenizer.AddSpecialTokens(added_tokens)); - } else { - // throw error - throw std::runtime_error( - "Need to pass the string list as to argument tokens"); - } - } else { - // throw error - std::ostringstream oss; - oss << "Expected number of arguments is 1, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def add_tokens(token) -static PyObject* AddTokens(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_tokens = NULL; - static char* kwlist[] = {const_cast("tokens"), NULL}; - bool flag_ = - PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &kw_tokens); - Py_ssize_t args_num = PyTuple_Size(args); - std::string tokens; - if (args_num == (Py_ssize_t)1) { - if (PyList_Check(kw_tokens)) { - std::vector added_tokens; - Py_ssize_t tokens_num = PyList_GET_SIZE(kw_tokens); - for (Py_ssize_t i = 0; i < tokens_num; ++i) { - PyObject* obj = PyList_GetItem(kw_tokens, i); - if (PyUnicode_Check(obj)) { - added_tokens.push_back( - core::AddedToken(CastPyArg2AttrString(obj, 0), true)); - } else { - py::handle py_obj(obj); - if (!py::type::of(py_obj).is(py::type::of())) { - throw std::runtime_error( - "The argument of tokens should be List[Union[str, " - "AddedToken]]"); - } - auto added_token = py_obj.cast(); - added_tokens.push_back(added_token); - } - } - return ToPyObject(self->tokenizer.AddTokens(added_tokens)); - } else { - throw std::runtime_error( - "Need to pass the string list as to argument tokens"); - } - } else { - std::ostringstream oss; - oss << "Expected number of arguments is 1, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def enable_padding(direction="right", pad_id=0, -// pad_type_id=0, pad_token="[PAD]", -// length=None, ad_to_multiple_of=None) -static PyObject* EnablePadding(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_direction = NULL; - PyObject* kw_pad_id = NULL; - PyObject* kw_pad_type_id = NULL; - PyObject* kw_pad_token = NULL; - PyObject* kw_length = NULL; - PyObject* kw_pad_to_multiple_of = NULL; - bool flag_kwargs = false; - if (kwargs) flag_kwargs = true; - static char* kwlist[] = {const_cast("direction"), - const_cast("pad_id"), - const_cast("pad_type_id"), - const_cast("pad_token"), - const_cast("length"), - const_cast("pad_to_multiple_of"), - NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords(args, - kwargs, - "|OOOOOO", - kwlist, - &kw_direction, - &kw_pad_id, - &kw_pad_type_id, - &kw_pad_token, - &kw_length, - &kw_pad_to_multiple_of); - Py_ssize_t args_num = PyTuple_Size(args); - std::string direction = "right"; - uint32_t pad_id = 0; - uint32_t pad_type_id = 0; - std::string pad_token = "[PAD]"; - uint32_t* length_ptr = nullptr; - uint32_t* pad_to_multiple_of_ptr = nullptr; - uint32_t length = 0; - uint32_t pad_to_multiple_of = 0; - VLOG(6) << "args_num: " << args_num << ", flag_kwargs: " << flag_kwargs; - VLOG(6) << "kw_direction: " << kw_direction; - VLOG(6) << "kw_pad_id: " << kw_pad_id; - VLOG(6) << "kw_pad_type_id: " << kw_pad_type_id; - VLOG(6) << "kw_pad_token: " << kw_pad_token; - VLOG(6) << "kw_length: " << kw_length; - VLOG(6) << "kw_pad_to_multiple_of: " << kw_pad_to_multiple_of; - if (args_num >= (Py_ssize_t)0 && args_num <= (Py_ssize_t)6) { - if ((args_num == 0 && flag_kwargs && kw_direction) || (args_num >= 1)) { - direction = CastPyArg2AttrString(kw_direction, 0); - } - if ((args_num <= 1 && flag_kwargs && kw_pad_id) || (args_num >= 2)) { - pad_id = CastPyArg2AttrSize_t(kw_pad_id, 1); - } - if ((args_num <= 2 && flag_kwargs && kw_pad_type_id) || (args_num >= 3)) { - pad_type_id = CastPyArg2AttrSize_t(kw_pad_type_id, 2); - } - if ((args_num <= 3 && flag_kwargs && kw_pad_token) || (args_num >= 4)) { - pad_token = CastPyArg2AttrString(kw_pad_token, 3); - } - if ((args_num <= 4 && flag_kwargs && kw_length) || (args_num >= 5)) { - if (!(kw_length == Py_None)) { - length = CastPyArg2AttrSize_t(kw_length, 4); - length_ptr = &length; - } - } - if ((args_num <= 5 && flag_kwargs && kw_pad_to_multiple_of) || - (args_num == 6)) { - if (!(kw_pad_to_multiple_of == Py_None)) { - pad_to_multiple_of = CastPyArg2AttrSize_t(kw_pad_to_multiple_of, 5); - pad_to_multiple_of_ptr = &pad_to_multiple_of; - } - } - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 0 to 6, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - core::Direction pad_direction; - if (direction == "right") { - pad_direction = core::Direction::RIGHT; - } else if (direction == "left") { - pad_direction = core::Direction::LEFT; - } else { - throw std::runtime_error( - "The direction args should be \"right\" or \"left\""); - } - self->tokenizer.EnablePadMethod(pad_direction, - pad_id, - pad_type_id, - pad_token, - length_ptr, - pad_to_multiple_of_ptr); - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def disable_padding() -static PyObject* DisablePadding(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - Py_ssize_t args_num = PyTuple_Size(args); - if (args_num == (Py_ssize_t)0) { - self->tokenizer.DisablePadMethod(); - Py_RETURN_NONE; - } else { - std::ostringstream oss; - oss << "Expected number of arguments is 0, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def enable_truncation(max_length, stride=0, strategy="longest_first", -// direction="right") -static PyObject* EnableTruncation(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_max_length = NULL; - PyObject* kw_stride = NULL; - PyObject* kw_strategy = NULL; - PyObject* kw_direction = NULL; - bool flag_kwargs = false; - if (kwargs) flag_kwargs = true; - static char* kwlist[] = {const_cast("max_length"), - const_cast("stride"), - const_cast("strategy"), - const_cast("direction"), - NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords(args, - kwargs, - "|OOOO", - kwlist, - &kw_max_length, - &kw_stride, - &kw_strategy, - &kw_direction); - Py_ssize_t args_num = PyTuple_Size(args); - uint32_t max_length = 0; - uint32_t stride = 0; - std::string strategy = "longest_first"; - std::string direction = "right"; - - if (args_num >= (Py_ssize_t)0 && args_num <= (Py_ssize_t)4) { - max_length = CastPyArg2AttrSize_t(kw_max_length, 0); - if ((args_num <= 1 && flag_kwargs && kw_stride) || (args_num >= 2)) { - stride = CastPyArg2AttrSize_t(kw_stride, 1); - } - if ((args_num <= 2 && flag_kwargs && kw_strategy) || (args_num >= 3)) { - strategy = CastPyArg2AttrString(kw_strategy, 2); - } - if ((args_num <= 3 && flag_kwargs && kw_direction) || (args_num >= 4)) { - direction = CastPyArg2AttrString(kw_direction, 3); - } - } else { - std::ostringstream oss; - oss << "Expected number of arguments 1 to 4, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - - core::TruncStrategy trunc_strategy; - if (strategy == "longest_first") { - trunc_strategy = core::TruncStrategy::LONGEST_FIRST; - } else if (strategy == "only_first") { - trunc_strategy = core::TruncStrategy::ONLY_FIRST; - } else if (strategy == "only_second") { - trunc_strategy = core::TruncStrategy::ONLY_SECOND; - } else { - throw std::runtime_error( - "The strategy args should be \"longest_first\", \"only_first\" or " - "\"only_second\""); - } - core::Direction trunc_direction; - if (direction == "right") { - trunc_direction = core::Direction::RIGHT; - } else if (direction == "left") { - trunc_direction = core::Direction::LEFT; - } else { - throw std::runtime_error( - "The direction args should be \"right\" or \"left\""); - } - self->tokenizer.EnableTruncMethod( - max_length, stride, trunc_direction, trunc_strategy); - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def disable_truncation() -static PyObject* DisableTruncation(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - Py_ssize_t args_num = PyTuple_Size(args); - if (args_num == (Py_ssize_t)0) { - self->tokenizer.DisableTruncMethod(); - Py_RETURN_NONE; - } else { - std::ostringstream oss; - oss << "Expected number of arguments is 0, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def get_vocab(with_added_vocabulary=True) -static PyObject* GetVocab(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_with_added_vocabulary = NULL; - static char* kwlist[] = {const_cast("with_added_vocabulary"), NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords( - args, kwargs, "|O", kwlist, &kw_with_added_vocabulary); - Py_ssize_t args_num = PyTuple_Size(args); - bool with_added_vocabulary = true; - if (args_num == (Py_ssize_t)0) { - with_added_vocabulary = true; - py::object py_obj = - py::cast(self->tokenizer.GetVocab(with_added_vocabulary)); - py_obj.inc_ref(); - return py_obj.ptr(); - } else if (args_num == (Py_ssize_t)1) { - if (PyBool_Check(kw_with_added_vocabulary)) { - with_added_vocabulary = - CastPyArg2AttrBoolean(kw_with_added_vocabulary, 0); - py::object py_obj = - py::cast(self->tokenizer.GetVocab(with_added_vocabulary)); - py_obj.inc_ref(); - return py_obj.ptr(); - } else { - // throw error - } - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 0 to 1, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def get_vocab_size(with_added_vocabulary=True) -static PyObject* GetVocabSize(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_with_added_vocabulary = NULL; - static char* kwlist[] = {const_cast("with_added_vocabulary"), NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords( - args, kwargs, "|O", kwlist, &kw_with_added_vocabulary); - Py_ssize_t args_num = PyTuple_Size(args); - bool with_added_vocabulary = true; - if (args_num == (Py_ssize_t)0) { - with_added_vocabulary = true; - return ToPyObject(self->tokenizer.GetVocabSize(with_added_vocabulary)); - } else if (args_num == (Py_ssize_t)1) { - if (PyBool_Check(kw_with_added_vocabulary)) { - with_added_vocabulary = - CastPyArg2AttrBoolean(kw_with_added_vocabulary, 0); - return ToPyObject(self->tokenizer.GetVocabSize(with_added_vocabulary)); - } else { - // throw error - } - } else { - std::ostringstream oss; - oss << "Expected number of arguments is 0, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def encode(sequence, pair=None, is_pretokenized=False, -// add_special_tokens=True) -static PyObject* Encode(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_sequence = NULL; - PyObject* kw_pair = NULL; - PyObject* kw_is_pretokenized = NULL; - PyObject* kw_add_special_tokens = NULL; - bool flag_kwargs = false; - if (kwargs) flag_kwargs = true; - static char* kwlist[] = {const_cast("sequence"), - const_cast("pair"), - const_cast("is_pretokenized"), - const_cast("add_special_tokens"), - NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords(args, - kwargs, - "|OOOO", - kwlist, - &kw_sequence, - &kw_pair, - &kw_is_pretokenized, - &kw_add_special_tokens); - Py_ssize_t args_num = PyTuple_Size(args); - if (args_num >= (Py_ssize_t)1 && args_num <= (Py_ssize_t)4) { - bool is_pretokenized = false; - bool add_special_tokens = true; - bool has_pair = false; - core::Encoding encoding; - core::Encoding pair_encoding; - core::Encoding result_encoding; - - if ((args_num <= 2 && flag_kwargs && kw_is_pretokenized) || - (args_num >= 3)) { - is_pretokenized = CastPyArg2AttrBoolean(kw_is_pretokenized, 2); - } - if ((args_num <= 3 && flag_kwargs && kw_add_special_tokens) || - (args_num >= 4)) { - add_special_tokens = CastPyArg2AttrBoolean(kw_add_special_tokens, 3); - } - if (is_pretokenized) { - if (PyList_Check(kw_sequence) || PyTuple_Check(kw_sequence)) { - std::vector sequence_array = - CastPyArg2VectorOfStr(kw_sequence, 0); - std::vector pair_array; - if ((args_num <= 1 && flag_kwargs && kw_pair && kw_pair != Py_None) || - (args_num >= 2)) { - has_pair = true; - pair_array = CastPyArg2VectorOfStr(kw_pair, 1); - } - self->tokenizer.EncodeSingleString( - sequence_array, 0, core::OffsetType::CHAR, &encoding); - core::Encoding* pair_encoding_ptr = nullptr; - if (has_pair) { - self->tokenizer.EncodeSingleString( - pair_array, 0, core::OffsetType::CHAR, &pair_encoding); - pair_encoding_ptr = &pair_encoding; - } - self->tokenizer.PostProcess( - &encoding, pair_encoding_ptr, add_special_tokens, &result_encoding); - } else { - // throw error - std::ostringstream oss; - oss << "The sequence should be list of string when " - "is_pretokenized=True"; - throw std::runtime_error(oss.str()); - } - } else { - std::string sequence = CastPyArg2AttrString(kw_sequence, 0); - std::string pair; - if (((args_num <= 1 && flag_kwargs && kw_pair) || (args_num >= 2)) && - kw_pair != Py_None) { - has_pair = true; - pair = CastPyArg2AttrString(kw_pair, 1); - } - self->tokenizer.EncodeSingleString( - sequence, 0, core::OffsetType::CHAR, &encoding); - core::Encoding* pair_encoding_ptr = nullptr; - if (has_pair) { - self->tokenizer.EncodeSingleString( - pair, 1, core::OffsetType::CHAR, &pair_encoding); - pair_encoding_ptr = &pair_encoding; - } - self->tokenizer.PostProcess( - &encoding, pair_encoding_ptr, add_special_tokens, &result_encoding); - } - py::object py_obj = py::cast(result_encoding); - py_obj.inc_ref(); - return py_obj.ptr(); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 1 to 4, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def encode(input, add_special_tokens=True, is_pretokenized=False) -static PyObject* EncodeBatch(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_input = NULL; - PyObject* kw_special_tokens = NULL; - PyObject* kw_is_pretokenized = NULL; - bool flag_kwargs = false; - if (kwargs) flag_kwargs = true; - static char* kwlist[] = {const_cast("input"), - const_cast("add_special_tokens"), - const_cast("is_pretokenized"), - NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords(args, - kwargs, - "|OOO", - kwlist, - &kw_input, - &kw_special_tokens, - &kw_is_pretokenized); - bool add_special_tokens = true; - bool is_pretokenized = false; - Py_ssize_t args_num = PyTuple_Size(args); - VLOG(6) << " args_num: " << args_num << ", flag_kwargs: " << flag_kwargs - << ", flag_: " << flag_; - std::vector batch_encode_input; - if (args_num >= (Py_ssize_t)1 && args_num <= (Py_ssize_t)3) { - if ((args_num <= 1 && flag_kwargs && kw_special_tokens) || - (args_num >= 2)) { - add_special_tokens = CastPyArg2AttrBoolean(kw_special_tokens, 1); - } - if ((args_num <= 2 && kw_is_pretokenized && flag_kwargs) || args_num == 3) { - is_pretokenized = CastPyArg2AttrBoolean(kw_is_pretokenized, 2); - } - if (PyList_Check(kw_input)) { - Py_ssize_t list_size = PyList_Size(kw_input); - for (Py_ssize_t i = 0; i < list_size; ++i) { - PyObject* item = PyList_GetItem(kw_input, i); - // Has pair - if (PyTuple_Check(item) && PyTuple_Size(item) == 2) { - PyObject* text = PyTuple_GetItem(item, 0); - PyObject* text_pair = PyTuple_GetItem(item, 1); - // pretokenized - if (is_pretokenized) { - Py_ssize_t pretokenized_size = PyList_Size(item); - std::vector text_vec; - std::vector text_pair_vec; - for (Py_ssize_t j = 0; j < pretokenized_size; ++j) { - PyObject* py_text = PyList_GetItem(text, j); - PyObject* py_text_pair = PyList_GetItem(text_pair, j); - text_vec.push_back(CastPyArg2AttrString(py_text, 0)); - text_pair_vec.push_back(CastPyArg2AttrString(py_text_pair, 1)); - } - batch_encode_input.push_back( - std::pair{text_vec, - text_pair_vec}); - } else { - batch_encode_input.push_back( - std::pair{ - CastPyArg2AttrString(text, 0), - CastPyArg2AttrString(text_pair, 1)}); - } - } else { - // Only get text - if (is_pretokenized) { - Py_ssize_t pretokenized_size = PyList_Size(item); - std::vector str_vec(pretokenized_size); - for (Py_ssize_t j = 0; j < pretokenized_size; ++j) { - PyObject* py_text = PyList_GetItem(item, j); - str_vec[j] = CastPyArg2AttrString(py_text, 0); - } - batch_encode_input.push_back(str_vec); - } else { - batch_encode_input.push_back(CastPyArg2AttrString(item, 0)); - } - } - } - } else { - std::ostringstream oss; - oss << "Expected the type of input argument is list"; - throw std::runtime_error(oss.str()); - } - std::vector result_encodings; - self->tokenizer.EncodeBatchStrings( - batch_encode_input, &result_encodings, add_special_tokens); - py::object py_obj = py::cast(result_encodings); - py_obj.inc_ref(); - return py_obj.ptr(); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 1 to 2, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def id_to_token(id) -static PyObject* IdToToken(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - PyObject* kw_id = NULL; - static char* kwlist[] = {const_cast("id"), NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &kw_id); - Py_ssize_t args_num = PyTuple_Size(args); - if (args_num == (Py_ssize_t)1) { - if (PyLong_Check(kw_id)) { - uint32_t id = PyLong_AsLong(kw_id); - std::string token; - if (self->tokenizer.IdToToken(id, &token)) { - return ToPyObject(token); - } - Py_RETURN_NONE; - } else { - // throw error - } - } else { - std::ostringstream oss; - oss << "Expected number of arguments is 1, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; -} - -// def token_to_id(token) -static PyObject* TokenToId(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_token = NULL; - static char* kwlist[] = {const_cast("token"), NULL}; - bool flag_ = - PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &kw_token); - Py_ssize_t args_num = PyTuple_Size(args); - std::string token = ""; - if (args_num == (Py_ssize_t)1) { - token = CastPyArg2AttrString(kw_token, 0); - uint32_t id; - if (self->tokenizer.TokenToId(token, &id)) { - return ToPyObject(id); - } - Py_RETURN_NONE; - } else { - std::ostringstream oss; - oss << "Expected number of arguments is 1, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def num_special_tokens_to_add(is_pair) -static PyObject* NumSpecialTokensToAdd(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_is_pair = NULL; - static char* kwlist[] = {const_cast("is_pair"), NULL}; - bool flag_ = - PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &kw_is_pair); - Py_ssize_t args_num = PyTuple_Size(args); - bool is_pair = false; - if (args_num == (Py_ssize_t)1) { - is_pair = CastPyArg2AttrBoolean(kw_is_pair, 0); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is 1, but recive " << args_num; - throw std::runtime_error(oss.str()); - } - auto postprocessor_ptr = self->tokenizer.GetPostProcessorPtr(); - if (postprocessor_ptr == nullptr) { - return ToPyObject(0); - } - return ToPyObject(postprocessor_ptr->AddedTokensNum(is_pair)); - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - - -static PyObject* Save(TokenizerObject* self, PyObject* args, PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_path = NULL; - PyObject* kw_pretty = NULL; - static char* kwlist[] = { - const_cast("path"), const_cast("pretty"), NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords( - args, kwargs, "|OO", kwlist, &kw_path, &kw_pretty); - bool pretty = true; - Py_ssize_t args_num = PyTuple_Size(args); - if (args_num >= (Py_ssize_t)1 && args_num <= (Py_ssize_t)2) { - if (args_num == (Py_ssize_t)2) { - pretty = CastPyArg2AttrBoolean(kw_pretty, 1); - } - std::string path = CastPyArg2AttrString(kw_path, 0); - self->tokenizer.Save(path, pretty); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 1 to 2, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - Py_RETURN_NONE; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -static PyObject* ToStr(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_pretty = NULL; - static char* kwlist[] = {const_cast("pretty"), NULL}; - bool flag_ = - PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &kw_pretty); - bool pretty = true; - Py_ssize_t args_num = PyTuple_Size(args); - std::string json_str; - if (args_num >= (Py_ssize_t)0 && args_num <= (Py_ssize_t)1) { - if (args_num == (Py_ssize_t)1) { - pretty = CastPyArg2AttrBoolean(kw_pretty, 0); - } - self->tokenizer.ToJsonStr(&json_str, pretty); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 1 to 2, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - return ToPyObject(json_str); - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -static PyObject* FromStr(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_json = NULL; - static char* kwlist[] = {const_cast("json"), NULL}; - bool flag_ = - PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &kw_json); - Py_ssize_t args_num = PyTuple_Size(args); - std::string json_str; - core::Tokenizer tokenizer; - if (args_num == (Py_ssize_t)1) { - json_str = CastPyArg2AttrString(kw_json, 0); - tokenizer = core::Tokenizer::LoadFromStr(json_str); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 1 to 2, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - TokenizerObject* obj = - (TokenizerObject*)TokenizerNew(p_tokenizer_type, NULL, NULL); - obj->tokenizer = tokenizer; - return (PyObject*)obj; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -static PyObject* FromFile(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_path = NULL; - static char* kwlist[] = {const_cast("json"), NULL}; - bool flag_ = - PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &kw_path); - Py_ssize_t args_num = PyTuple_Size(args); - std::string path; - core::Tokenizer tokenizer; - if (args_num == (Py_ssize_t)1) { - path = CastPyArg2AttrString(kw_path, 0); - tokenizer = core::Tokenizer::LoadFromFile(path); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 1 to 2, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - TokenizerObject* obj = - (TokenizerObject*)TokenizerNew(p_tokenizer_type, NULL, NULL); - obj->tokenizer = tokenizer; - return (PyObject*)obj; - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def decode(self, ids, skip_special_tokens=True): -static PyObject* Decode(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_ids = NULL; - PyObject* kw_skip_special_tokens = NULL; - bool flag_kwargs = false; - if (kwargs) flag_kwargs = true; - static char* kwlist[] = { - const_cast("ids"), const_cast("skip_special_tokens"), NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords( - args, kwargs, "|OO", kwlist, &kw_ids, &kw_skip_special_tokens); - bool skip_special_tokens = true; - Py_ssize_t args_num = PyTuple_Size(args); - VLOG(6) << " args_num: " << args_num << ", flag_kwargs: " << flag_kwargs - << ", flag_: " << flag_; - if (args_num >= (Py_ssize_t)1 && args_num <= (Py_ssize_t)2) { - if (args_num == (Py_ssize_t)2 || (flag_kwargs && kw_skip_special_tokens)) { - skip_special_tokens = CastPyArg2AttrBoolean(kw_skip_special_tokens, 1); - } - auto ids = CastPyArg2VectorOfInt(kw_ids, 0); - std::string result; - self->tokenizer.Decode(ids, &result, skip_special_tokens); - return ToPyObject(result); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 1 to 2, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -// def decode_batch(self, sequences, skip_special_tokens=True): -static PyObject* DecodeBatch(TokenizerObject* self, - PyObject* args, - PyObject* kwargs) { - TOKENIZERS_TRY - PyObject* kw_sequences = NULL; - PyObject* kw_skip_special_tokens = NULL; - bool flag_kwargs = false; - if (kwargs) flag_kwargs = true; - static char* kwlist[] = {const_cast("sequences"), - const_cast("skip_special_tokens"), - NULL}; - bool flag_ = PyArg_ParseTupleAndKeywords( - args, kwargs, "|OO", kwlist, &kw_sequences, &kw_skip_special_tokens); - bool skip_special_tokens = true; - Py_ssize_t args_num = PyTuple_Size(args); - VLOG(6) << " args_num: " << args_num << ", flag_kwargs: " << flag_kwargs - << ", flag_: " << flag_; - if (args_num >= (Py_ssize_t)1 && args_num <= (Py_ssize_t)2) { - if (args_num == (Py_ssize_t)2 || (flag_kwargs && kw_skip_special_tokens)) { - skip_special_tokens = CastPyArg2AttrBoolean(kw_skip_special_tokens, 1); - } - std::vector> batch_ids; - PyObject* item = nullptr; - if (PyTuple_Check(kw_sequences)) { - Py_ssize_t len = PyTuple_Size(kw_sequences); - for (Py_ssize_t i = 0; i < len; i++) { - item = PyTuple_GetItem(kw_sequences, i); - batch_ids.emplace_back(CastPyArg2VectorOfInt(item, 0)); - } - } else if (PyList_Check(kw_sequences)) { - Py_ssize_t len = PyList_Size(kw_sequences); - for (Py_ssize_t i = 0; i < len; i++) { - item = PyList_GetItem(kw_sequences, i); - batch_ids.emplace_back(CastPyArg2VectorOfInt(item, 0)); - } - } else { - std::ostringstream oss; - oss << "Args sequences need to be int of list or tuple"; - throw std::runtime_error(oss.str()); - } - std::vector result; - self->tokenizer.DecodeBatch(batch_ids, &result, skip_special_tokens); - return ToPyObject(result); - } else { - std::ostringstream oss; - oss << "Expected number of arguments is from 1 to 2, but recive " - << args_num; - throw std::runtime_error(oss.str()); - } - TOKENIZERS_CATCH_AND_THROW_RETURN_NULL -} - -PyMethodDef tokenizer_variable_methods[] = { - {"add_special_tokens", - (PyCFunction)(void (*)(void))AddSpecialTokens, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"add_tokens", - (PyCFunction)(void (*)(void))AddTokens, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"enable_padding", - (PyCFunction)(void (*)(void))EnablePadding, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"disable_padding", - (PyCFunction)(void (*)(void))DisablePadding, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"enable_truncation", - (PyCFunction)(void (*)(void))EnableTruncation, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"disable_truncation", - (PyCFunction)(void (*)(void))DisableTruncation, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"get_vocab", - (PyCFunction)(void (*)(void))GetVocab, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"get_vocab_size", - (PyCFunction)(void (*)(void))GetVocabSize, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"encode", - (PyCFunction)(void (*)(void))Encode, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"encode_batch", - (PyCFunction)(void (*)(void))EncodeBatch, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"decode", - (PyCFunction)(void (*)(void))Decode, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"decode_batch", - (PyCFunction)(void (*)(void))DecodeBatch, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"id_to_token", - (PyCFunction)(void (*)(void))IdToToken, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"token_to_id", - (PyCFunction)(void (*)(void))TokenToId, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"num_special_tokens_to_add", - (PyCFunction)(void (*)(void))NumSpecialTokensToAdd, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"save", - (PyCFunction)(void (*)(void))Save, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"to_str", - (PyCFunction)(void (*)(void))ToStr, - METH_VARARGS | METH_KEYWORDS, - NULL}, - {"from_str", - (PyCFunction)(void (*)(void))FromStr, - METH_VARARGS | METH_KEYWORDS | METH_STATIC, - NULL}, - {"from_file", - (PyCFunction)(void (*)(void))FromFile, - METH_VARARGS | METH_KEYWORDS | METH_STATIC, - NULL}, - // TODO(zhoushunjie): Need to implement - // {"from_buffer", - // (PyCFunction)(void (*)(void))NumSpecialTokensToAdd, - // METH_VARARGS | METH_KEYWORDS | METH_STATIC, - // NULL}, - // {"from_pretrained", - // (PyCFunction)(void (*)(void))NumSpecialTokensToAdd, - // METH_VARARGS | METH_KEYWORDS | METH_STATIC, - // NULL}, - {NULL, NULL, 0, NULL}}; - -void BindTokenizers(pybind11::module* m) { - auto heap_type = reinterpret_cast( - PyType_Type.tp_alloc(&PyType_Type, 0)); - heap_type->ht_name = ToPyObject("Tokenizer"); - heap_type->ht_qualname = ToPyObject("Tokenizer"); - auto type = &heap_type->ht_type; - type->tp_name = "Tokenizer"; - type->tp_basicsize = sizeof(TokenizerObject); - type->tp_dealloc = (destructor)TokenizerDealloc; - type->tp_as_number = &number_methods; - type->tp_as_sequence = &sequence_methods; - type->tp_as_mapping = &mapping_methods; - type->tp_methods = tokenizer_variable_methods; - type->tp_getset = tokenizer_variable_properties; - type->tp_init = TokenizerInit; - type->tp_new = TokenizerNew; - Py_INCREF(&PyBaseObject_Type); - type->tp_base = reinterpret_cast(&PyBaseObject_Type); - type->tp_flags |= - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE; -#if PY_VERSION_HEX >= 0x03050000 - type->tp_as_async = &heap_type->as_async; -#endif - p_tokenizer_type = type; - - if (PyType_Ready(type) < 0) { - throw "Init Tokenizers error in BindTokenizers(PyType_Ready)."; - return; - } - - Py_INCREF(type); - if (PyModule_AddObject( - m->ptr(), "Tokenizer", reinterpret_cast(type)) < 0) { - Py_DECREF(type); - Py_DECREF(m->ptr()); - throw "Init Tokenizers error in BindTokenizers(PyModule_AddObject)."; - return; - } -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/tokenizers.h b/fast_tokenizer/fast_tokenizer/pybind/tokenizers.h deleted file mode 100644 index b9e45b957da8..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/tokenizers.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -void BindTokenizers(pybind11::module* m); - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/utils.cc b/fast_tokenizer/fast_tokenizer/pybind/utils.cc deleted file mode 100644 index 377aeb2ed32e..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/utils.cc +++ /dev/null @@ -1,277 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include - -#include "fast_tokenizer/pybind/utils.h" -namespace py = pybind11; - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -PyObject* ToPyObject(bool value) { - if (value) { - Py_INCREF(Py_True); - return Py_True; - } else { - Py_INCREF(Py_False); - return Py_False; - } -} - -PyObject* ToPyObject(int value) { return PyLong_FromLong(value); } - -PyObject* ToPyObject(uint32_t value) { return PyLong_FromUnsignedLong(value); } - -PyObject* ToPyObject(int64_t value) { return PyLong_FromLongLong(value); } - -PyObject* ToPyObject(size_t value) { return PyLong_FromSize_t(value); } - -PyObject* ToPyObject(float value) { return PyLong_FromDouble(value); } - -PyObject* ToPyObject(double value) { return PyLong_FromDouble(value); } - -PyObject* ToPyObject(const char* value) { return PyUnicode_FromString(value); } - -PyObject* ToPyObject(const std::string& value) { - return PyUnicode_FromString(value.c_str()); -} - -PyObject* ToPyObject(const std::vector& value) { - PyObject* result = PyList_New((Py_ssize_t)value.size()); - - for (size_t i = 0; i < value.size(); i++) { - PyList_SET_ITEM(result, static_cast(i), ToPyObject(value[i])); - } - - return result; -} - -PyObject* ToPyObject(const std::vector& value) { - PyObject* result = PyList_New((Py_ssize_t)value.size()); - - for (size_t i = 0; i < value.size(); i++) { - PyList_SET_ITEM(result, static_cast(i), ToPyObject(value[i])); - } - - return result; -} - -PyObject* ToPyObject(const std::vector& value) { - PyObject* result = PyList_New((Py_ssize_t)value.size()); - - for (size_t i = 0; i < value.size(); i++) { - PyList_SET_ITEM(result, (Py_ssize_t)i, ToPyObject(value[i])); - } - - return result; -} - -PyObject* ToPyObject(const std::vector& value) { - PyObject* result = PyList_New((Py_ssize_t)value.size()); - - for (size_t i = 0; i < value.size(); i++) { - PyList_SET_ITEM(result, (Py_ssize_t)i, ToPyObject(value[i])); - } - - return result; -} - -PyObject* ToPyObject(const std::vector& value) { - PyObject* result = PyList_New((Py_ssize_t)value.size()); - - for (size_t i = 0; i < value.size(); i++) { - PyList_SET_ITEM(result, static_cast(i), ToPyObject(value[i])); - } - - return result; -} - -PyObject* ToPyObject(const std::vector& value) { - PyObject* result = PyList_New((Py_ssize_t)value.size()); - - for (size_t i = 0; i < value.size(); i++) { - PyList_SET_ITEM(result, static_cast(i), ToPyObject(value[i])); - } - - return result; -} - -PyObject* ToPyObject(const std::vector>& value) { - PyObject* result = PyList_New((Py_ssize_t)value.size()); - - for (size_t i = 0; i < value.size(); i++) { - PyList_SET_ITEM(result, static_cast(i), ToPyObject(value[i])); - } - - return result; -} - -PyObject* ToPyObject(const std::vector& value) { - PyObject* result = PyList_New((Py_ssize_t)value.size()); - for (size_t i = 0; i < value.size(); i++) { - PyList_SET_ITEM(result, static_cast(i), ToPyObject(value[i])); - } - return result; -} - -bool CastPyArg2AttrBoolean(PyObject* obj, ssize_t arg_pos) { - if (obj == Py_None) { - return false; // To be compatible with QA integration testing. Some - // test case pass in None. - } else if (obj == Py_True) { - return true; - } else if (obj == Py_False) { - return false; - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 << " must be bool, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - } - return false; -} - -std::string CastPyArg2AttrString(PyObject* obj, ssize_t arg_pos) { - if (PyUnicode_Check(obj)) { - Py_ssize_t size; - const char* data; - data = PyUnicode_AsUTF8AndSize(obj, &size); - return std::string(data, static_cast(size)); - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 << " must be str, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - return ""; - } -} - -int CastPyArg2AttrInt(PyObject* obj, ssize_t arg_pos) { - if (PyLong_Check(obj) && !PyBool_Check(obj)) { - return static_cast(PyLong_AsLong(obj)); - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 << " must be str, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - return 0; - } -} - -int64_t CastPyArg2AttrLong(PyObject* obj, ssize_t arg_pos) { - if (PyLong_Check(obj) && !PyBool_Check(obj)) { - return (int64_t)PyLong_AsLong(obj); // NOLINT - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 << " must be str, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - return 0; - } -} - -size_t CastPyArg2AttrSize_t(PyObject* obj, ssize_t arg_pos) { - if (PyLong_Check(obj) && !PyBool_Check(obj)) { - return PyLong_AsSize_t(obj); - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 << " must be str, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - return 0; - } -} - -float CastPyArg2AttrFloat(PyObject* obj, ssize_t arg_pos) { - if (PyFloat_Check(obj) || PyLong_Check(obj)) { - return static_cast(PyFloat_AsDouble(obj)); - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 << " must be str, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - return 0; - } -} - -std::vector CastPyArg2VectorOfStr(PyObject* obj, size_t arg_pos) { - std::vector result; - if (PyList_Check(obj)) { - Py_ssize_t len = PyList_Size(obj); - PyObject* item = nullptr; - for (Py_ssize_t i = 0; i < len; i++) { - item = PyList_GetItem(obj, i); - if (PyUnicode_Check(item)) { - result.emplace_back(CastPyArg2AttrString(item, 0)); - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 - << " must be list of str, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - return {}; - } - } - } else if (PyTuple_Check(obj)) { - Py_ssize_t len = PyTuple_Size(obj); - PyObject* item = nullptr; - for (Py_ssize_t i = 0; i < len; i++) { - item = PyTuple_GetItem(obj, i); - if (PyUnicode_Check(item)) { - result.emplace_back(CastPyArg2AttrString(item, 0)); - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 - << " must be list of str, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - return {}; - } - } - } else if (obj == Py_None) { - return {}; - } else { - std::ostringstream oss; - oss << "argument (position" << arg_pos + 1 - << " must be list or tuple, but got " - << (reinterpret_cast(obj->ob_type))->tp_name; - throw std::runtime_error(oss.str()); - return {}; - } - return result; -} - -bool PyObject_CheckLongOrConvertToLong(PyObject** obj) { - if ((PyLong_Check(*obj) && !PyBool_Check(*obj))) { - return true; - } - - if (std::string((reinterpret_cast((*obj)->ob_type))->tp_name) - .find("numpy") != std::string::npos) { - auto to = PyNumber_Long(*obj); - if (to) { - *obj = to; - return true; - } - } - - return false; -} - -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/pybind/utils.h b/fast_tokenizer/fast_tokenizer/pybind/utils.h deleted file mode 100644 index 75ef55d654a5..000000000000 --- a/fast_tokenizer/fast_tokenizer/pybind/utils.h +++ /dev/null @@ -1,108 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include -#include -#include - -// For Windows ssize_t -#if defined(_MSC_VER) -#include -typedef SSIZE_T ssize_t; -#endif - -namespace paddlenlp { -namespace fast_tokenizer { -namespace pybind { - -PyObject* ToPyObject(int value); -PyObject* ToPyObject(uint32_t value); -PyObject* ToPyObject(bool value); -PyObject* ToPyObject(int64_t value); -PyObject* ToPyObject(size_t value); -PyObject* ToPyObject(float value); -PyObject* ToPyObject(double value); -PyObject* ToPyObject(const char* value); -PyObject* ToPyObject(const std::string& value); -PyObject* ToPyObject(const std::vector& value); -PyObject* ToPyObject(const std::vector& value); -PyObject* ToPyObject(const std::vector& value); -PyObject* ToPyObject(const std::vector& value); -PyObject* ToPyObject(const std::vector& value); -PyObject* ToPyObject(const std::vector& value); -PyObject* ToPyObject(const std::vector>& value); -PyObject* ToPyObject(const std::vector& value); - -bool PyObject_CheckLongOrConvertToLong(PyObject** obj); -bool CastPyArg2AttrBoolean(PyObject* obj, ssize_t arg_pos); -std::string CastPyArg2AttrString(PyObject* obj, ssize_t arg_pos); -int CastPyArg2AttrInt(PyObject* obj, ssize_t arg_pos); -int64_t CastPyArg2AttrLong(PyObject* obj, ssize_t arg_pos); -size_t CastPyArg2AttrSize_t(PyObject* obj, ssize_t arg_pos); -float CastPyArg2AttrFloat(PyObject* obj, ssize_t arg_pos); -std::vector CastPyArg2VectorOfStr(PyObject* obj, size_t arg_pos); - -template -std::vector CastPyArg2VectorOfInt(PyObject* obj, size_t arg_pos) { - std::vector result; - if (PyList_Check(obj)) { - Py_ssize_t len = PyList_Size(obj); - PyObject* item = nullptr; - for (Py_ssize_t i = 0; i < len; i++) { - item = PyList_GetItem(obj, i); - if (PyObject_CheckLongOrConvertToLong(&item)) { - result.emplace_back(static_cast(PyLong_AsLong(item))); - } else { - std::ostringstream oss; - oss << "argument (position " << arg_pos + 1 - << "must be list of int, but got " - << reinterpret_cast(item->ob_type)->tp_name - << " at pos " << i; - throw oss.str(); - return {}; - } - } - } else if (PyTuple_Check(obj)) { - Py_ssize_t len = PyTuple_Size(obj); - PyObject* item = nullptr; - for (Py_ssize_t i = 0; i < len; i++) { - item = PyTuple_GetItem(obj, i); - if (PyObject_CheckLongOrConvertToLong(&item)) { - result.emplace_back(static_cast(PyLong_AsLong(item))); - } else { - std::ostringstream oss; - oss << "argument (position " << arg_pos + 1 - << "must be list of int, but got " - << reinterpret_cast(item->ob_type)->tp_name - << " at pos " << i; - throw oss.str(); - return {}; - } - } - } else if (obj == Py_None) { - return {}; - } else { - std::ostringstream oss; - oss << "argument (position " << arg_pos + 1 - << "must be list or tuple, but got " - << reinterpret_cast(obj->ob_type)->tp_name; - throw oss.str(); - return {}; - } - return result; -} -} // namespace pybind -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/test/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/test/CMakeLists.txt deleted file mode 100644 index 380e63582b16..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/CMakeLists.txt +++ /dev/null @@ -1,58 +0,0 @@ -if(WITH_TESTING) -cc_library(tokenizers_gtest_main SRCS gtest_main.cc DEPS gtest gflags) - -# Test Normalizers modules -cc_test(test_normalizer SRCS test_normalizer.cc DEPS normalizers) -cc_test(test_unicode SRCS test_unicode.cc DEPS normalizers) -cc_test(test_replace SRCS test_replace.cc DEPS normalizers) -cc_test(test_strip SRCS test_strip.cc DEPS normalizers) -cc_test(test_utils SRCS test_utils.cc DEPS normalizers) - -# Test PreTokenizers modules -cc_test(test_whitespace SRCS test_whitespace.cc DEPS pretokenizers) -cc_test(test_bert_pretokenizer SRCS test_bert_pretokenizer.cc DEPS pretokenizers) -cc_test(test_split_pretokenizer SRCS test_split_pretokenizer.cc DEPS pretokenizers) - -# Test Model -cc_test(test_wordpiece SRCS test_wordpiece.cc DEPS models) -cc_test(test_fast_wordpiece SRCS test_fast_wordpiece.cc DEPS models) - -# Download ernie vocab for test -set(ERNIE_VOCAB_PATH ${CMAKE_CURRENT_BINARY_DIR}/ernie_vocab.txt) -if (EXISTS ${ERNIE_VOCAB_PATH}) - message("The ${ERNIE_VOCAB_PATH} exists already.") -else() - file(DOWNLOAD "https://bj.bcebos.com/paddlenlp/models/transformers/ernie/vocab.txt" ${ERNIE_VOCAB_PATH} SHOW_PROGRESS) - message("Already download the vocab.txt of ernie to ${CMAKE_CURRENT_BINARY_DIR} for test.") -endif() - -# Download clip vocab and merge files -set(CLIP_VOCAB_PATH ${CMAKE_CURRENT_BINARY_DIR}/clip_vocab.json) -set(CLIP_MERGES_PATH ${CMAKE_CURRENT_BINARY_DIR}/clip_merges.txt) - -if (EXISTS ${CLIP_VOCAB_PATH}) - message("The ${CLIP_VOCAB_PATH} exists already.") -else() - file(DOWNLOAD "http://bj.bcebos.com/paddlenlp/models/community/openai/clip-vit-large-patch14/vocab.json" ${CLIP_VOCAB_PATH} SHOW_PROGRESS) - message("Already download the vocab.json of clip to ${CMAKE_CURRENT_BINARY_DIR} for test.") -endif() - -if (EXISTS ${CLIP_MERGES_PATH}) - message("The ${CLIP_MERGES_PATH} exists already.") -else() - file(DOWNLOAD "http://bj.bcebos.com/paddlenlp/models/community/openai/clip-vit-large-patch14/merges.txt" ${CLIP_MERGES_PATH} SHOW_PROGRESS) - message("Already download the merges.txt of clip to ${CMAKE_CURRENT_BINARY_DIR} for test.") -endif() - -# Test Tokenizer -cc_test(test_bert_tokenizer SRCS test_bert_tokenizer.cc DEPS normalizers pretokenizers models postprocessors tokenizer) - -# Test PostProcessor -cc_test(test_roberta_postprocessor SRCS test_roberta_postprocessor.cc DEPS normalizers pretokenizers models postprocessors tokenizer) - -if(NOT WITH_PYTHON) - cc_test(test_ernie_fast_tokenizer SRCS test_ernie_fast_tokenizer.cc DEPS normalizers pretokenizers models postprocessors tokenizer core_tokenizers) - cc_test(test_clip_fast_tokenizer SRCS test_clip_fast_tokenizer.cc DEPS normalizers pretokenizers models postprocessors tokenizer core_tokenizers) -endif() - -endif() diff --git a/fast_tokenizer/fast_tokenizer/test/gtest_main.cc b/fast_tokenizer/fast_tokenizer/test/gtest_main.cc deleted file mode 100644 index 8dc400abf0be..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/gtest_main.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserve. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "gtest/gtest.h" -#include "gflags/gflags.h" -#include "glog/logging.h" - - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - std::vector new_argv; - for (int i = 0; i < argc; ++i) { - new_argv.push_back(argv[i]); - } - - std::vector envs; - std::vector undefok; - - char* env_str = nullptr; - if (envs.size() > 0) { - std::string env_string = "--tryfromenv="; - for (auto t : envs) { - env_string += t + ","; - } - env_string = env_string.substr(0, env_string.length() - 1); - env_str = strdup(env_string.c_str()); - new_argv.push_back(env_str); - VLOG(1) << "gtest env_string:" << env_string; - } - - char* undefok_str = nullptr; - if (undefok.size() > 0) { - std::string undefok_string = "--undefok="; - for (auto t : undefok) { - undefok_string += t + ","; - } - undefok_string = undefok_string.substr(0, undefok_string.length() - 1); - undefok_str = strdup(undefok_string.c_str()); - new_argv.push_back(undefok_str); - VLOG(1) << "gtest undefok_string:" << undefok_string; - } - - int new_argc = static_cast(new_argv.size()); - char** new_argv_address = new_argv.data(); - ::GFLAGS_NAMESPACE::ParseCommandLineFlags( - &new_argc, &new_argv_address, false); - int ret = RUN_ALL_TESTS(); - if (env_str) free(env_str); - if (undefok_str) free(undefok_str); - return ret; -} diff --git a/fast_tokenizer/fast_tokenizer/test/test_bert_pretokenizer.cc b/fast_tokenizer/fast_tokenizer/test/test_bert_pretokenizer.cc deleted file mode 100644 index f4be133e375d..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_bert_pretokenizer.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include "fast_tokenizer/pretokenizers/bert.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { -TEST(pretokenizers, bert) { - std::string input = - "I \t am good\r at \nsport. I like\tfootball especially!!!"; - std::vector expected_outputs = {"I", - "am", - "good", - "at", - "sport", - ".", - "I", - "like", - "football", - "especially", - "!", - "!", - "!"}; - pretokenizers::PreTokenizedString bert_input(input); - pretokenizers::BertPreTokenizer()(&bert_input); - ASSERT_EQ(expected_outputs.size(), bert_input.GetSplitsSize()); - for (int i = 0; i < expected_outputs.size(); ++i) { - ASSERT_EQ(bert_input.GetSplit(i).normalized_.GetStr(), expected_outputs[i]); - } -} -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_bert_tokenizer.cc b/fast_tokenizer/fast_tokenizer/test/test_bert_tokenizer.cc deleted file mode 100644 index 6aac0fdf1222..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_bert_tokenizer.cc +++ /dev/null @@ -1,120 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ -#include -#include -#include -#include "fast_tokenizer/core/added_vocabulary.h" -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/core/tokenizer.h" -#include "fast_tokenizer/models/wordpiece.h" -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/postprocessors/bert.h" -#include "fast_tokenizer/pretokenizers/bert.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -template -void CheckVectorEqual(const std::vector& a, const std::vector& b) { - ASSERT_EQ(a.size(), b.size()); - auto size = a.size(); - for (int i = 0; i < size; ++i) { - ASSERT_EQ(a[i], b[i]); - } -} - -TEST(tokenizer, bert_tokenizer) { - models::WordPieceFactory factory; - std::string vocab_file = "ernie_vocab.txt"; - factory.SetFiles(vocab_file); - // Declare the components of tokenizer - auto word_piece = factory.CreateWordPieceModel(); - auto normalizer = normalizers::BertNormalizer(); - auto pretokenizer = pretokenizers::BertPreTokenizer(); - auto postprocessor = - postprocessors::BertPostProcessor({"[SEP]", 2}, {"[CLS]", 1}); - core::PadMethod pad_method; - core::TruncMethod trunc_method; - - // Initialize tokenizer - core::Tokenizer tokenizer(word_piece); - tokenizer.SetNormalizer(normalizer); - tokenizer.SetPreTokenizer(pretokenizer); - tokenizer.SetPostProcessor(postprocessor); - tokenizer.SetPadMethod(pad_method); - tokenizer.SetTruncMethod(trunc_method); - std::vector special_added_tokens = { - {"[PAD]", true}, - {"[CLS]", true}, - {"[SEP]", true}, - {"[MASK]", true}, - {"[UNK]", true}, - }; - auto special_tokens_num = tokenizer.AddSpecialTokens(special_added_tokens); - - // Tokenize the sample strings - std::vector encodings(2); - tokenizer.EncodePairStrings("今天天气真好", &encodings[0]); - tokenizer.EncodePairStrings("don't know how this missed award nominations.", - &encodings[1]); - std::vector> expected_tokens = { - {"[CLS]", "今", "天", "天", "气", "真", "好", "[SEP]"}, - {"[CLS]", - "don", - "[UNK]", - "t", - "know", - "how", - "this", - "miss", - "##ed", - "award", - "no", - "##min", - "##ations", - ".", - "[SEP]"}}; - std::vector> expected_ids = { - {1, 508, 125, 125, 266, 384, 170, 2}, - {1, - 3362, - 17963, - 2052, - 3821, - 5071, - 3730, - 7574, - 9530, - 6301, - 3825, - 10189, - 11005, - 42, - 2}}; - std::vector> expected_type_ids = { - {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; - for (int i = 0; i < encodings.size(); ++i) { - CheckVectorEqual(expected_tokens[i], encodings[i].GetTokens()); - CheckVectorEqual(expected_ids[i], encodings[i].GetIds()); - CheckVectorEqual(expected_type_ids[i], encodings[i].GetTypeIds()); - } -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_clip_fast_tokenizer.cc b/fast_tokenizer/fast_tokenizer/test/test_clip_fast_tokenizer.cc deleted file mode 100644 index b2604b3cc906..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_clip_fast_tokenizer.cc +++ /dev/null @@ -1,50 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/tokenizers/clip_fast_tokenizer.h" - -#include "fast_tokenizer/test/utils.h" - -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(tokenizer, clip_full) { - std::string vocab_path = "clip_vocab.json"; - std::string merges_path = "clip_merges.txt"; - tokenizers_impl::ClipFastTokenizer clip_tokenizer(vocab_path, merges_path); - - core::Encoding encoding; - std::string input_text = "A\n'll 11p223RF☆ho!!to?'d'd''d of a cat"; - std::vector expected_ids = { - 49406, 320, 1342, 272, 272, 335, 273, 273, 274, 16368, 13439, 2971, - 748, 531, 13610, 323, 1896, 8445, 323, 539, 320, 2368, 49407}; - std::vector expected_tokens = { - "<|startoftext|>", "a", "'ll", "1", "1", - "p", "2", "2", "3", "rf", - "âĺĨ", "ho", "!!", "to", "?'", - "d", "'d", "''", "d", "of", - "a", "cat", "<|endoftext|>"}; - clip_tokenizer.EncodePairStrings(input_text, &encoding); - CheckVectorEqual(expected_ids, encoding.GetIds()); - CheckVectorEqual(expected_tokens, encoding.GetTokens()); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_ernie_fast_tokenizer.cc b/fast_tokenizer/fast_tokenizer/test/test_ernie_fast_tokenizer.cc deleted file mode 100644 index f3b73f6b42d9..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_ernie_fast_tokenizer.cc +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include -#include "fast_tokenizer/core/added_vocabulary.h" -#include "fast_tokenizer/core/base.h" -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/core/tokenizer.h" -#include "fast_tokenizer/models/wordpiece.h" -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/postprocessors/bert.h" -#include "fast_tokenizer/pretokenizers/bert.h" -#include "fast_tokenizer/test/utils.h" -#include "fast_tokenizer/tokenizers/ernie_fast_tokenizer.h" - -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(tokenizer, ernie_fast_tokenizer) { - std::string vocab_file = "ernie_vocab.txt"; - tokenizers_impl::ErnieFastTokenizer ernie_fast_tokenizer(vocab_file); - std::vector encodings(2); - ernie_fast_tokenizer.EncodePairStrings("今天天气真好", &encodings[0]); - ernie_fast_tokenizer.EncodePairStrings( - "don't know how this missed award nominations.", &encodings[1]); - std::vector> expected_tokens = { - {"[CLS]", "今", "天", "天", "气", "真", "好", "[SEP]"}, - {"[CLS]", - "don", - "[UNK]", - "t", - "know", - "how", - "this", - "miss", - "##ed", - "award", - "no", - "##min", - "##ations", - ".", - "[SEP]"}}; - std::vector> expected_ids = { - {1, 508, 125, 125, 266, 384, 170, 2}, - {1, - 3362, - 17963, - 2052, - 3821, - 5071, - 3730, - 7574, - 9530, - 6301, - 3825, - 10189, - 11005, - 42, - 2}}; - std::vector> expected_type_ids = { - {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; - for (int i = 0; i < encodings.size(); ++i) { - CheckVectorEqual(expected_tokens[i], encodings[i].GetTokens()); - CheckVectorEqual(expected_ids[i], encodings[i].GetIds()); - CheckVectorEqual(expected_type_ids[i], encodings[i].GetTypeIds()); - } -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_fast_wordpiece.cc b/fast_tokenizer/fast_tokenizer/test/test_fast_wordpiece.cc deleted file mode 100644 index c30517ebd4a3..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_fast_wordpiece.cc +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include -#include "fast_tokenizer/models/fast_wordpiece.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(model, fast_wordpiece_token_to_id) { - auto vocab = models::FastWordPiece::GetVocabFromFile("ernie_vocab.txt"); - models::FastWordPiece fast_wordpiece_model(vocab); - // Test tokens in vocab - for (const auto& item : vocab) { - uint32_t id; - fast_wordpiece_model.TokenToId(item.first, &id); - ASSERT_EQ(item.second, id); - } - // Test [UNK] token - uint32_t fast_wordpiece_id; - ASSERT_FALSE(fast_wordpiece_model.TokenToId("dasd", &fast_wordpiece_id)); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_normalizer.cc b/fast_tokenizer/fast_tokenizer/test/test_normalizer.cc deleted file mode 100644 index 97de7ea83073..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_normalizer.cc +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/normalizers/replace.h" -#include "fast_tokenizer/normalizers/strip.h" -#include "fast_tokenizer/normalizers/unicode.h" -#include "glog/logging.h" -#include "gtest/gtest.h" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(normalizers, split) { - re2::RE2 pattern("-"); - std::string input = "The-final--countdown"; - normalizers::NormalizedString split_input(input); - auto test_split = [&pattern, &split_input]( - core::SplitMode mode, const std::vector expected_strings) { - std::vector normalizes; - split_input.Split(pattern, mode, &normalizes); - ASSERT_EQ(expected_strings.size(), normalizes.size()); - for (int i = 0; i < expected_strings.size(); ++i) { - ASSERT_EQ(expected_strings[i], normalizes[i].GetStr()); - } - }; - - test_split(core::SplitMode::REMOVED, {"The", "final", "countdown"}); - test_split(core::SplitMode::ISOLATED, - {"The", "-", "final", "-", "-", "countdown"}); - test_split(core::SplitMode::CONTIGUOUS, - {"The", "-", "final", "--", "countdown"}); - test_split(core::SplitMode::MERGED_WITH_PREVIOUS, - {"The-", "final-", "-", "countdown"}); - test_split(core::SplitMode::MERGED_WITH_NEXT, - {"The", "-final", "-", "-countdown"}); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_replace.cc b/fast_tokenizer/fast_tokenizer/test/test_replace.cc deleted file mode 100644 index c32a193e4e92..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_replace.cc +++ /dev/null @@ -1,47 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/normalizers/replace.h" -#include "fast_tokenizer/normalizers/strip.h" -#include "fast_tokenizer/normalizers/unicode.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(normalizers, replace) { - std::string input = "This is a ''test''"; - std::string expected_output = "This is a \"test\""; - normalizers::NormalizedString replace_input(input); - - normalizers::ReplaceNormalizer normalizer("''", "\""); - normalizer(&replace_input); - ASSERT_EQ(expected_output, replace_input.GetStr()); - - // Regex pattern - std::string regex_input = "This is a test"; - std::string expected_regex_output = "This is a test"; - normalizers::NormalizedString regex_replace_input(regex_input); - normalizers::ReplaceNormalizer regex_normalizer("(\\s+)", " "); - regex_normalizer(®ex_replace_input); - ASSERT_EQ(expected_regex_output, regex_replace_input.GetStr()); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_roberta_postprocessor.cc b/fast_tokenizer/fast_tokenizer/test/test_roberta_postprocessor.cc deleted file mode 100644 index 3fabbf6af049..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_roberta_postprocessor.cc +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include - -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/postprocessors/roberta.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(postprocessors, roberta) { - postprocessors::RobertaPostProcessor postprocessor; - core::Encoding encoding( - {core::Token(12, "Hello", {0, 5}), core::Token(14, "there", {6, 11})}, 0); - core::Encoding pair_encoding({core::Token(15, "pair", {0, 4})}, 0); - core::Encoding result_encoding; - - core::Encoding encoding_copy = encoding; - core::Encoding pair_encoding_copy = pair_encoding; - - postprocessor(&encoding_copy, nullptr, true, &result_encoding); - uint32_t special_word_idx = std::numeric_limits::max(); - ASSERT_EQ(result_encoding, - core::Encoding({0, 12, 14, 2}, - {0, 0, 0, 0}, - {"", "Hello", "there", ""}, - std::vector(4, special_word_idx), - {{0, 0}, {0, 5}, {6, 11}, {0, 0}}, - {1, 0, 0, 1}, - {1, 1, 1, 1}, - {}, - {{0, {1, 3}}})); - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(2), - std::vector(1, 0)); - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(3).size(), 0); - - encoding_copy = encoding; - postprocessor(&encoding_copy, &pair_encoding_copy, true, &result_encoding); - ASSERT_EQ( - result_encoding, - core::Encoding({0, 12, 14, 2, 2, 15, 2}, - {0, 0, 0, 0, 0, 0, 0}, - {"", "Hello", "there", "", "", "pair", ""}, - std::vector(7, special_word_idx), - {{0, 0}, {0, 5}, {6, 11}, {0, 0}, {0, 0}, {0, 4}, {0, 0}}, - {1, 0, 0, 1, 1, 0, 1}, - {1, 1, 1, 1, 1, 1, 1}, - {}, - {{0, {1, 3}}, {1, {5, 6}}})); - - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(2), - std::vector(1, 0)); - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(3), std::vector{}); - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(4), std::vector{}); - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(5), std::vector{1}); - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(6), std::vector{}); - - encoding_copy = encoding; - pair_encoding_copy = pair_encoding; - postprocessor(&encoding_copy, &pair_encoding_copy, false, &result_encoding); - ASSERT_EQ(result_encoding, - core::Encoding({12, 14, 15}, - {0, 0, 0}, - {"Hello", "there", "pair"}, - std::vector(3, special_word_idx), - {{0, 5}, {6, 11}, {0, 4}}, - {0, 0, 0}, - {1, 1, 1}, - {}, - {{0, {0, 2}}, {1, {2, 3}}})); - - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(0), std::vector{0}); - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(1), std::vector{0}); - ASSERT_EQ(result_encoding.TokenIdxToSequenceIds(2), std::vector{1}); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_split_pretokenizer.cc b/fast_tokenizer/fast_tokenizer/test/test_split_pretokenizer.cc deleted file mode 100644 index 89c4df06943e..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_split_pretokenizer.cc +++ /dev/null @@ -1,110 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include "fast_tokenizer/pretokenizers/split.h" -#include "glog/logging.h" -#include "gtest/gtest.h" -#include "re2/re2.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(pretokenizers, split_basic) { - std::string input = "How are you doing?"; - // All tokens' id are set to 0. - std::vector>> test_cases = - {{ - core::SplitMode::REMOVED, - std::vector{{0, "How", {0, 3}}, - {0, "are", {4, 7}}, - {0, "you", {8, 11}}, - {0, "doing", {12, 17}}, - {0, "?", {17, 18}}}, - }, - { - core::SplitMode::ISOLATED, - std::vector{{0, "How", {0, 3}}, - {0, " ", {3, 4}}, - {0, "are", {4, 7}}, - {0, " ", {7, 8}}, - {0, "you", {8, 11}}, - {0, " ", {11, 12}}, - {0, "doing", {12, 17}}, - {0, "?", {17, 18}}}, - }, - { - core::SplitMode::MERGED_WITH_PREVIOUS, - std::vector{{0, "How ", {0, 4}}, - {0, "are ", {4, 8}}, - {0, "you ", {8, 12}}, - {0, "doing", {12, 17}}, - {0, "?", {17, 18}}}, - }, - { - core::SplitMode::MERGED_WITH_NEXT, - std::vector{{0, "How", {0, 3}}, - {0, " are", {3, 7}}, - {0, " you", {7, 11}}, - {0, " doing", {11, 17}}, - {0, "?", {17, 18}}}, - }, - { - core::SplitMode::CONTIGUOUS, - std::vector{{0, "How", {0, 3}}, - {0, " ", {3, 4}}, - {0, "are", {4, 7}}, - {0, " ", {7, 8}}, - {0, "you", {8, 11}}, - {0, " ", {11, 12}}, - {0, "doing?", {12, 18}}}, - }}; - std::string pattern = R"(\w+|[^\w\s]+)"; - for (auto&& test : test_cases) { - pretokenizers::PreTokenizedString pretokenized(input); - pretokenizers::SplitPreTokenizer pretok(pattern, test.first, true); - pretok(&pretokenized); - ASSERT_EQ(test.second.size(), pretokenized.GetSplitsSize()); - for (int i = 0; i < test.second.size(); ++i) { - auto&& curr_split = pretokenized.GetSplit(i); - ASSERT_EQ(test.second[i].value_, curr_split.normalized_.GetStr()); - auto original_offset = curr_split.normalized_.GetOrginalOffset(); - ASSERT_EQ(test.second[i].offset_, original_offset); - } - } -} - -TEST(pretokenizers, split_invert) { - std::string input = "Hello Hello Hello"; - pretokenizers::PreTokenizedString pretok_str(input), - pretok_str_for_invert(input); - pretokenizers::SplitPreTokenizer pretok(" ", core::SplitMode::REMOVED, false); - pretokenizers::SplitPreTokenizer pretok_invert( - "Hello", core::SplitMode::REMOVED, true); - - pretok(&pretok_str); - pretok_invert(&pretok_str_for_invert); - - ASSERT_EQ(pretok_str.GetSplitsSize(), pretok_str_for_invert.GetSplitsSize()); - for (int i = 0; i < pretok_str.GetSplitsSize(); ++i) { - ASSERT_EQ(pretok_str.GetSplit(i).normalized_.GetStr(), - pretok_str_for_invert.GetSplit(i).normalized_.GetStr()); - } -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_strip.cc b/fast_tokenizer/fast_tokenizer/test/test_strip.cc deleted file mode 100644 index 370ad9c37d79..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_strip.cc +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/normalizers/replace.h" -#include "fast_tokenizer/normalizers/strip.h" -#include "fast_tokenizer/normalizers/unicode.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(normalizers, strip) { - std::string input = " \t我爱中国\n\f\v"; - std::string expected_lstrip_output = "我爱中国\n\f\v"; - std::string expected_rstrip_output = " \t我爱中国"; - std::string expected_lrstrip_output = "我爱中国"; - - normalizers::NormalizedString lrstrip_input(input); - normalizers::NormalizedString lstrip_input(input); - normalizers::NormalizedString rstrip_input(input); - - normalizers::StripNormalizer lrstrip(true, true); - lrstrip(&lrstrip_input); - std::string lrstrip_output = lrstrip_input.GetStr(); - ASSERT_EQ(expected_lrstrip_output, lrstrip_output); - - normalizers::StripNormalizer lstrip(true, false); - lstrip(&lstrip_input); - std::string lstrip_output = lstrip_input.GetStr(); - ASSERT_EQ(expected_lstrip_output, lstrip_output); - - normalizers::StripNormalizer rstrip(false, true); - rstrip(&rstrip_input); - std::string rstrip_output = rstrip_input.GetStr(); - ASSERT_EQ(expected_rstrip_output, rstrip_output); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_unicode.cc b/fast_tokenizer/fast_tokenizer/test/test_unicode.cc deleted file mode 100644 index e4e927b13f35..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_unicode.cc +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/normalizers/replace.h" -#include "fast_tokenizer/normalizers/strip.h" -#include "fast_tokenizer/normalizers/unicode.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(normalizers, unicode) { - std::string input = "\u1e9b\u0323a\u1e9b\u0323"; - std::string expected_nfkc_output = "ṩaṩ"; - std::string expected_nfc_output = "\u1e9b\u0323a\u1e9b\u0323"; - std::string expected_nfkd_output = "\u0073\u0323\u0307a\u0073\u0323\u0307"; - std::string expected_nfd_output = "\u017f\u0323\u0307a\u017f\u0323\u0307"; - - normalizers::NFKCNormalizer nfkc; - normalizers::NormalizedString normalized_input1(input); - normalizers::NormalizedString normalized_input2(input); - normalizers::NormalizedString normalized_input3(input); - normalizers::NormalizedString normalized_input4(input); - nfkc(&normalized_input1); - std::string nfkc_output = normalized_input1.GetStr(); - ASSERT_EQ(expected_nfkc_output, nfkc_output); - - normalizers::NFCNormalizer nfc; - nfc(&normalized_input2); - std::string nfc_output = normalized_input2.GetStr(); - ASSERT_EQ(expected_nfc_output, nfc_output); - - normalizers::NFKDNormalizer nfkd; - nfkd(&normalized_input3); - std::string nfkd_output = normalized_input3.GetStr(); - ASSERT_EQ(expected_nfkd_output, nfkd_output); - - normalizers::NFDNormalizer nfd; - nfd(&normalized_input4); - std::string nfd_output = normalized_input4.GetStr(); - ASSERT_EQ(expected_nfd_output, nfd_output); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_utils.cc b/fast_tokenizer/fast_tokenizer/test/test_utils.cc deleted file mode 100644 index 8711f47724b4..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_utils.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include "fast_tokenizer/normalizers/bert.h" -#include "fast_tokenizer/normalizers/replace.h" -#include "fast_tokenizer/normalizers/strip.h" -#include "fast_tokenizer/normalizers/unicode.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(normalizers, utils) { - std::string input = "ÓÓßSSCHLOË"; - std::string expected_output = "óóßsschloë"; - normalizers::NormalizedString lower_input(input); - lower_input.Lowercase(); - ASSERT_EQ(expected_output, lower_input.GetStr()); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_whitespace.cc b/fast_tokenizer/fast_tokenizer/test/test_whitespace.cc deleted file mode 100644 index 95a2fdee98b2..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_whitespace.cc +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include "fast_tokenizer/pretokenizers/whitespace.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(pretokenizers, whitespace) { - std::string input = "I \t am good\r at \nsport"; - std::vector expected_outputs = { - "I", "am", "good", "at", "sport"}; - pretokenizers::PreTokenizedString whitespace_input(input); - pretokenizers::WhitespacePreTokenizer()(&whitespace_input); - ASSERT_EQ(expected_outputs.size(), whitespace_input.GetSplitsSize()); - for (int i = 0; i < expected_outputs.size(); ++i) { - ASSERT_EQ(whitespace_input.GetSplit(i).normalized_.GetStr(), - expected_outputs[i]); - } -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/test_wordpiece.cc b/fast_tokenizer/fast_tokenizer/test/test_wordpiece.cc deleted file mode 100644 index 3d7661f92319..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/test_wordpiece.cc +++ /dev/null @@ -1,96 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include -#include "fast_tokenizer/models/wordpiece.h" -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -TEST(model, wordpiece_factory) { - models::WordPieceFactory factory; - auto check_config = [&](const std::string& filename, - size_t vocab_size, - const std::string& unk_token, - size_t max_input_chars_per_word, - const std::string& continuing_subword_prefix) { - ASSERT_EQ(filename, factory.config_.files_); - ASSERT_EQ(vocab_size, factory.config_.vocab_.size()); - ASSERT_EQ(unk_token, factory.config_.unk_token_); - ASSERT_EQ(max_input_chars_per_word, - factory.config_.max_input_chars_per_word_); - ASSERT_EQ(continuing_subword_prefix, - factory.config_.continuing_subword_prefix_); - }; - check_config("", 0, "[UNK]", 100, "##"); - - std::string vocab_file = "ernie_vocab.txt"; - factory.SetFiles(vocab_file); - factory.GetVocabFromFiles(vocab_file); - check_config(vocab_file, 17964UL, "[UNK]", 100, "##"); -} - -TEST(model, wordpiece_model) { - models::WordPieceFactory factory; - factory.SetFiles("ernie_vocab.txt"); - - auto wordpiece_model = factory.CreateWordPieceModel(); - auto check_token_id = [&](const std::string& expected_token, - uint32_t expected_id) { - std::string token; - uint32_t id; - wordpiece_model.TokenToId(expected_token, &id); - wordpiece_model.IdToToken(expected_id, &token); - ASSERT_EQ(id, expected_id); - ASSERT_EQ(token, expected_token); - }; - std::array tokens = { - "[PAD]", "[CLS]", "[SEP]", "[MASK]", ",", "的", "、", "一", "人", "有"}; - for (int i = 0; i < tokens.size(); i++) { - check_token_id(tokens[i], i); - } - // check non-exist token - uint32_t id; - ASSERT_FALSE(wordpiece_model.TokenToId("xxsada", &id)); - // check non-exist id - std::string token; - ASSERT_FALSE( - wordpiece_model.IdToToken(wordpiece_model.GetVocabSize(), &token)); - - // Check Tokenize Chinese - auto chinese_tokens = wordpiece_model.Tokenize("今天天气真好"); - auto check_token = [](const core::Token& token, - const std::string& expected_string, - uint32_t id, - core::Offset offset) { - ASSERT_EQ(token.value_, expected_string); - ASSERT_EQ(token.id_, id); - ASSERT_EQ(token.offset_, offset); - }; - check_token(chinese_tokens[0], "今", 508, {0, 3}); - check_token(chinese_tokens[1], "##天", 12172, {3, 6}); - check_token(chinese_tokens[2], "##天", 12172, {6, 9}); - check_token(chinese_tokens[3], "##气", 12311, {9, 12}); - check_token(chinese_tokens[4], "##真", 12427, {12, 15}); - check_token(chinese_tokens[5], "##好", 12217, {15, 18}); -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/test/utils.h b/fast_tokenizer/fast_tokenizer/test/utils.h deleted file mode 100644 index af1525ebbf1d..000000000000 --- a/fast_tokenizer/fast_tokenizer/test/utils.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include -#include -#include - -#include "glog/logging.h" -#include "gtest/gtest.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tests { - -template -void CheckVectorEqual(const std::vector& a, const std::vector& b) { - ASSERT_EQ(a.size(), b.size()); - auto size = a.size(); - for (int i = 0; i < size; ++i) { - ASSERT_EQ(a[i], b[i]); - } -} - -} // namespace tests -} // namespace fast_tokenizer -} // namespace paddlenlp \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/tokenizers/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/tokenizers/CMakeLists.txt deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/fast_tokenizer/fast_tokenizer/tokenizers/clip_fast_tokenizer.cc b/fast_tokenizer/fast_tokenizer/tokenizers/clip_fast_tokenizer.cc deleted file mode 100644 index 929d6320cf7b..000000000000 --- a/fast_tokenizer/fast_tokenizer/tokenizers/clip_fast_tokenizer.cc +++ /dev/null @@ -1,138 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/tokenizers/clip_fast_tokenizer.h" -#include "fast_tokenizer/models/models.h" -#include "fast_tokenizer/normalizers/normalizers.h" -#include "fast_tokenizer/postprocessors/postprocessors.h" -#include "fast_tokenizer/pretokenizers/pretokenizers.h" -#include "glog/logging.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tokenizers_impl { - -ClipFastTokenizer::ClipFastTokenizer( - const std::string& vocab_path, - const std::string& merges_path, - uint32_t max_length, - const std::string& unk_token, - const std::string& pad_token, - const std::string& bos_token, - const std::string& eos_token, - bool add_prefix_space, - const std::string& continuing_subword_prefix, - const std::string& end_of_word_suffix, - bool trim_offsets) { - core::Vocab vocab; - core::Merges merges; - models::BPE::GetVocabAndMergesFromFile( - vocab_path, merges_path, &vocab, &merges); - VLOG(6) << "The vocab size of ClipFastTokenizer is " << vocab.size(); - VLOG(6) << "The merges size of ClipFastTokenizer is " << merges.size(); - - models::BPE bpe(vocab, - merges, - 10000, - {}, - {unk_token}, - {continuing_subword_prefix}, - {end_of_word_suffix}, - false); - // Set tokenizer model - this->SetModel(bpe); - - // Set added tokens - std::vector added_tokens; - uint32_t id; - unk_token_ = unk_token; - if (this->TokenToId(unk_token, &id)) { - added_tokens.emplace_back(unk_token, true); - } - pad_token_ = pad_token; - if (this->TokenToId(pad_token, &id)) { - added_tokens.emplace_back(pad_token, true); - pad_token_id_ = id; - } - bos_token_ = bos_token; - if (this->TokenToId(bos_token, &id)) { - added_tokens.emplace_back(bos_token, true); - bos_token_id_ = id; - } - eos_token_ = eos_token; - if (this->TokenToId(eos_token, &id)) { - added_tokens.emplace_back(eos_token, true); - eos_token_id_ = id; - } - this->AddSpecialTokens(added_tokens); - - // Set normalizers - normalizers::NFCNormalizer nfc_normalizer; - normalizers::ReplaceNormalizer replace_normalizer(R"(\s+)", " "); - normalizers::LowercaseNormalizer lower_normalizer; - normalizers::SequenceNormalizer seq_normalizer; - seq_normalizer.AppendNormalizer(&nfc_normalizer); - seq_normalizer.AppendNormalizer(&replace_normalizer); - seq_normalizer.AppendNormalizer(&lower_normalizer); - this->SetNormalizer(seq_normalizer); - - // Set pretokenizers - pretokenizers::ByteLevelPreTokenizer byte_level_pretokenizer(add_prefix_space, - true); - pretokenizers::SplitPreTokenizer split_pretokenizer( - R"('s|'t|'re|'ve|'m|'ll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+)", - core::SplitMode::REMOVED, - true); - pretokenizers::SequencePreTokenizer seq_pretokenizer; - seq_pretokenizer.AppendPreTokenizer(&split_pretokenizer); - seq_pretokenizer.AppendPreTokenizer(&byte_level_pretokenizer); - this->SetPreTokenizer(seq_pretokenizer); - - // Set postprocessors - postprocessors::RobertaPostProcessor roberta_postprocessor( - {eos_token, eos_token_id_}, - {bos_token, bos_token_id_}, - /* trim_offsets= */ false, - add_prefix_space); - this->SetPostProcessor(roberta_postprocessor); - - if (max_length == 0) { - this->DisableTruncMethod(); - } else { - this->EnableTruncMethod(max_length, - 0, - core::Direction::RIGHT, - core::TruncStrategy::LONGEST_FIRST); - } -} - -std::string ClipFastTokenizer::GetPadToken() const { return pad_token_; } - -uint32_t ClipFastTokenizer::GetPadTokenId() const { return pad_token_id_; } - -std::string ClipFastTokenizer::GetUNKToken() const { return unk_token_; } - -uint32_t ClipFastTokenizer::GetUNKTokenId() const { return unk_token_id_; } - -std::string ClipFastTokenizer::GetBOSToken() const { return bos_token_; } - -uint32_t ClipFastTokenizer::GetBOSTokenId() const { return bos_token_id_; } - -std::string ClipFastTokenizer::GetEOSToken() const { return eos_token_; } - -uint32_t ClipFastTokenizer::GetEOSTokenId() const { return eos_token_id_; } - -} // namespace tokenizers_impl -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/tokenizers/clip_fast_tokenizer.h b/fast_tokenizer/fast_tokenizer/tokenizers/clip_fast_tokenizer.h deleted file mode 100644 index c01891961556..000000000000 --- a/fast_tokenizer/fast_tokenizer/tokenizers/clip_fast_tokenizer.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/core/tokenizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tokenizers_impl { - -struct FASTTOKENIZER_DECL ClipFastTokenizer : public core::Tokenizer { - ClipFastTokenizer(const std::string& vocab_path, - const std::string& merges_path, - uint32_t max_length = 0, - const std::string& unk_token = "<|endoftext|>", - const std::string& pad_token = "<|endoftext|>", - const std::string& bos_token = "<|startoftext|>", - const std::string& eos_token = "<|endoftext|>", - bool add_prefix_space = false, - const std::string& continuing_subword_prefix = "", - const std::string& end_of_word_suffix = "", - bool trim_offsets = false); - std::string GetPadToken() const; - uint32_t GetPadTokenId() const; - std::string GetUNKToken() const; - uint32_t GetUNKTokenId() const; - std::string GetBOSToken() const; - uint32_t GetBOSTokenId() const; - std::string GetEOSToken() const; - uint32_t GetEOSTokenId() const; - -private: - std::string pad_token_; - uint32_t pad_token_id_; - std::string unk_token_; - uint32_t unk_token_id_; - std::string bos_token_; - uint32_t bos_token_id_; - std::string eos_token_; - uint32_t eos_token_id_; -}; - -} // namespace fast_tokenizer_impl -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/tokenizers/ernie_fast_tokenizer.cc b/fast_tokenizer/fast_tokenizer/tokenizers/ernie_fast_tokenizer.cc deleted file mode 100644 index 2c9d3bacbd5c..000000000000 --- a/fast_tokenizer/fast_tokenizer/tokenizers/ernie_fast_tokenizer.cc +++ /dev/null @@ -1,152 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/tokenizers/ernie_fast_tokenizer.h" -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/models/models.h" -#include "fast_tokenizer/normalizers/normalizers.h" -#include "fast_tokenizer/postprocessors/postprocessors.h" -#include "fast_tokenizer/pretokenizers/pretokenizers.h" -#include "fast_tokenizer/utils/utils.h" -#include "glog/logging.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tokenizers_impl { - -ErnieFastTokenizer::ErnieFastTokenizer(const std::string& vocab_path, - const std::string& unk_token, - const std::string& sep_token, - const std::string& cls_token, - const std::string& pad_token, - const std::string& mask_token, - bool clean_text, - bool handle_chinese_chars, - bool strip_accents, - bool lowercase, - const std::string& wordpieces_prefix, - uint32_t max_sequence_len) { - core::Vocab vocab; - utils::GetVocabFromFiles(vocab_path, &vocab); - VLOG(6) << "The vocab size of ErnieFastTokenizer is " << vocab.size(); - Init(vocab, - unk_token, - sep_token, - cls_token, - pad_token, - mask_token, - clean_text, - handle_chinese_chars, - strip_accents, - lowercase, - wordpieces_prefix, - max_sequence_len); -} - - -ErnieFastTokenizer::ErnieFastTokenizer(const core::Vocab& vocab, - const std::string& unk_token, - const std::string& sep_token, - const std::string& cls_token, - const std::string& pad_token, - const std::string& mask_token, - bool clean_text, - bool handle_chinese_chars, - bool strip_accents, - bool lowercase, - const std::string& wordpieces_prefix, - uint32_t max_sequence_len) { - Init(vocab, - unk_token, - sep_token, - cls_token, - pad_token, - mask_token, - clean_text, - handle_chinese_chars, - strip_accents, - lowercase, - wordpieces_prefix, - max_sequence_len); -} - - -void ErnieFastTokenizer::Init(const core::Vocab& vocab, - const std::string& unk_token, - const std::string& sep_token, - const std::string& cls_token, - const std::string& pad_token, - const std::string& mask_token, - bool clean_text, - bool handle_chinese_chars, - bool strip_accents, - bool lowercase, - const std::string& wordpieces_prefix, - uint32_t max_sequence_len) { - models::FastWordPiece wordpiece(vocab, - unk_token, - 100 /* max_input_chars_per_word */, - wordpieces_prefix, - true); - this->SetModel(wordpiece); - - std::vector added_tokens; - uint32_t id; - if (this->TokenToId(unk_token, &id)) { - added_tokens.emplace_back(unk_token, true); - } - if (this->TokenToId(sep_token, &id)) { - added_tokens.emplace_back(sep_token, true); - } - if (this->TokenToId(cls_token, &id)) { - added_tokens.emplace_back(cls_token, true); - } - if (this->TokenToId(pad_token, &id)) { - added_tokens.emplace_back(pad_token, true); - } - if (this->TokenToId(mask_token, &id)) { - added_tokens.emplace_back(mask_token, true); - } - this->AddSpecialTokens(added_tokens); - - - normalizers::BertNormalizer bert_normalizer( - clean_text, handle_chinese_chars, strip_accents, lowercase); - this->SetNormalizer(bert_normalizer); - - if (vocab.size() > 0) { - uint32_t sep_id, cls_id; - if (!this->TokenToId(sep_token, &sep_id)) { - throw std::invalid_argument("sep_token not found in the vocabulary"); - } - if (!this->TokenToId(cls_token, &cls_id)) { - throw std::invalid_argument("cls_token not found in the vocabulary"); - } - postprocessors::BertPostProcessor bert_postprocessor({sep_token, sep_id}, - {cls_token, cls_id}); - this->SetPostProcessor(bert_postprocessor); - } - if (max_sequence_len == 0) { - this->DisableTruncMethod(); - } else { - this->EnableTruncMethod(max_sequence_len, - 0, - core::Direction::RIGHT, - core::TruncStrategy::LONGEST_FIRST); - } -} - -} // namespace tokenizers_impl -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/tokenizers/ernie_fast_tokenizer.h b/fast_tokenizer/fast_tokenizer/tokenizers/ernie_fast_tokenizer.h deleted file mode 100644 index b1cf0dbe52b2..000000000000 --- a/fast_tokenizer/fast_tokenizer/tokenizers/ernie_fast_tokenizer.h +++ /dev/null @@ -1,70 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ -#pragma once - -#include -#include -#include "fast_tokenizer/core/encoding.h" -#include "fast_tokenizer/core/tokenizer.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace tokenizers_impl { - -struct FASTTOKENIZER_DECL ErnieFastTokenizer : public core::Tokenizer { - ErnieFastTokenizer(const std::string& vocab_path, - const std::string& unk_token = "[UNK]", - const std::string& sep_token = "[SEP]", - const std::string& cls_token = "[CLS]", - const std::string& pad_token = "[PAD]", - const std::string& mask_token = "[MASK]", - bool clean_text = true, - bool handle_chinese_chars = true, - bool strip_accents = true, - bool lowercase = true, - const std::string& wordpieces_prefix = "##", - uint32_t max_sequence_len = 0); - - ErnieFastTokenizer(const core::Vocab& vocab, - const std::string& unk_token = "[UNK]", - const std::string& sep_token = "[SEP]", - const std::string& cls_token = "[CLS]", - const std::string& pad_token = "[PAD]", - const std::string& mask_token = "[MASK]", - bool clean_text = true, - bool handle_chinese_chars = true, - bool strip_accents = true, - bool lowercase = true, - const std::string& wordpieces_prefix = "##", - uint32_t max_sequence_len = 0); - -private: - void Init(const core::Vocab& vocab, - const std::string& unk_token = "[UNK]", - const std::string& sep_token = "[SEP]", - const std::string& cls_token = "[CLS]", - const std::string& pad_token = "[PAD]", - const std::string& mask_token = "[MASK]", - bool clean_text = true, - bool handle_chinese_chars = true, - bool strip_accents = true, - bool lowercase = true, - const std::string& wordpieces_prefix = "##", - uint32_t max_sequence_len = 0); -}; - -} // namespace fast_tokenizer_impl -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/CMakeLists.txt b/fast_tokenizer/fast_tokenizer/utils/CMakeLists.txt deleted file mode 100644 index d4ef2b1eb91f..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cc_library(utils SRCS utils.cc DEPS icuuc icudata) -cc_library(trie SRCS trie.cc DEPS dart utils) -cc_library(failure SRCS failure.cc DEPS trie utils) -cc_library(sentencepiece_normalizer SRCS sentencepiece_normalizer.cc DEPS trie icuuc icudata utils) -cc_library(lattice SRCS lattice.cc DEPS utils) \ No newline at end of file diff --git a/fast_tokenizer/fast_tokenizer/utils/cache.h b/fast_tokenizer/fast_tokenizer/utils/cache.h deleted file mode 100644 index 704710572394..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/cache.h +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include -#include -#include - -#include "fast_tokenizer/utils/shared_mutex.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -static size_t DEFAULT_CACHE_CAPACITY = 10000; -typedef utils::shared_mutex RWLock; -typedef std::unique_lock WLock; -typedef utils::shared_lock RLock; - -template -struct Cache { - std::unordered_map map_; - size_t capacity_; - Cache(size_t capacity = DEFAULT_CACHE_CAPACITY) : capacity_(capacity) { - Fresh(); - } - - Cache(const Cache& other) { - RLock guard(cache_mutex_); - map_ = other.map_; - capacity_ = other.capacity_; - } - - Cache& operator=(const Cache& other) { - RLock guard(cache_mutex_); - map_ = other.map_; - capacity_ = other.capacity_; - return *this; - } - - void Fresh() { CreateCacheMap(capacity_); } - void Clear() { - WLock guard(cache_mutex_); - map_.clear(); - } - - bool GetValue(const K& key, V* value) { - // It's not guaranteed to get the value if the key is in cache - // for non-blocking read. - if (cache_mutex_.try_lock_shared()) { - if (map_.find(key) == map_.end()) { - cache_mutex_.unlock_shared(); - return false; - } - *value = map_.at(key); - cache_mutex_.unlock_shared(); - return true; - } - return false; - } - - bool SetValue(const K& key, const V& value) { - // Before trying to acquire a write lock, we check if we are already at - // capacity with a read handler. - if (cache_mutex_.try_lock_shared()) { - if (map_.size() >= capacity_) { - cache_mutex_.unlock_shared(); - return false; - } - } else { - return false; - } - if (cache_mutex_.try_lock()) { - map_.insert({key, value}); - cache_mutex_.unlock(); - return true; - } - return false; - } - -private: - void CreateCacheMap(size_t capacity) { - WLock guard(cache_mutex_); - map_ = std::unordered_map(capacity); - } - RWLock cache_mutex_; -}; - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/failure.cc b/fast_tokenizer/fast_tokenizer/utils/failure.cc deleted file mode 100644 index 1ae50b4d334e..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/failure.cc +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright 2022 TF.Text Authors. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include - -#include "glog/logging.h" -#include "fast_tokenizer/utils/failure.h" -#include "fast_tokenizer/utils/trie.h" -#include "fast_tokenizer/utils/utf8.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -Failure::Failure() - : failure_link_(utils::kNullNode), - failure_pops_offset_length_(utils::kNullFailurePopsList) {} - -FailureVocabToken::FailureVocabToken( - const std::string& token, - int token_id, - const std::string& continuing_subword_prefix) - : token_(token), - token_id_(token_id), - is_suffix_token_(false), - actual_token_start_offset_(0), - actual_token_unicode_len_(0), - contains_punctuation_(false) { - if (!continuing_subword_prefix.empty() && - token_ != continuing_subword_prefix && - utils::IsSuffixWord(token_, continuing_subword_prefix)) { - is_suffix_token_ = true; - actual_token_start_offset_ = continuing_subword_prefix.size(); - } - // Iterate over the Unicode chars from the token, to initialize - // contains_punctuation_ and actual_token_unicode_len_. - int token_len = token.size(); - int cur_pos = actual_token_start_offset_; - uint32_t ch; - const char* pSrc = token.c_str(); - while (cur_pos < token_len) { - uint32_t count = utils::UTF8ToUInt32(pSrc + cur_pos, &ch); - cur_pos += count; - ch = utils::UTF8ToUnicode(ch); - if (!contains_punctuation_ && utils::IsPunctuationOrChineseChar(ch)) { - contains_punctuation_ = true; - } - ++actual_token_unicode_len_; - } -} - -const std::string& FailureVocabToken::Token() const { return token_; } - -int FailureVocabToken::TokenId() const { return token_id_; } - -bool FailureVocabToken::IsSuffixToken() const { return is_suffix_token_; } - -bool FailureVocabToken::ContainsPunctuation() const { - return contains_punctuation_; -} - -int FailureVocabToken::TokenUnicodeLengthWithoutContinuingSubwordPrefix() - const { - return actual_token_unicode_len_; -} - -int FailureVocabToken::TokenLengthWithoutContinuingSubwordPrefix() const { - return token_.size() - actual_token_start_offset_; -} - -void FailureArray::BuildFailureVocab( - const std::unordered_map& vocab, - const std::string& unk_token, - const std::string& continuing_subword_prefix) { - if (vocab.size() > utils::kMaxSupportedVocabSize) { - std::ostringstream oss; - oss << "Vocab size exceeds the max supported (" - << utils::kMaxSupportedVocabSize - << "). Found vocab size: " << vocab.size(); - throw std::invalid_argument(oss.str()); - } - failure_vocab_tokens_.reserve(vocab.size()); - int unk_id = vocab.at(unk_token); - for (auto& item : vocab) { - if (item.first == continuing_subword_prefix) { - VLOG(6) - << "The empty suffix token is found in the vocabulary, which takes " - "place in token id space but will (almost) never be used in the " - "result. Consider cleaning it from the vocabulary."; - continue; - } - if (item.first.empty()) { - VLOG(6) - << "The empty string is found in the vocabulary, which takes place " - "in the token id space but will never be used in the result. " - "Consider cleaning it from the vocabulary."; - continue; - } - FailureVocabToken vocab_token( - item.first, item.second, continuing_subword_prefix); - if (vocab_token.TokenLengthWithoutContinuingSubwordPrefix() > - utils::kMaxVocabTokenLengthInUTF8Bytes) { - std::ostringstream oss; - oss << "Vocab token utf8 length (excluding suffix indicator) exceeds the " - "max supported (" - << utils::kMaxVocabTokenLengthInUTF8Bytes - << "). The vocab token is: " << item.first - << " with utf8 length (excluding suffix indicator): " - << vocab_token.TokenLengthWithoutContinuingSubwordPrefix(); - throw std::invalid_argument(oss.str()); - } - // Skip the word which contains punctuation but not a punctuation. - if (with_pretokenization_ && vocab_token.ContainsPunctuation() && - (vocab_token.IsSuffixToken() || - vocab_token.TokenUnicodeLengthWithoutContinuingSubwordPrefix() > 1)) { - continue; - } - failure_vocab_tokens_.emplace_back(vocab_token); - } - if (failure_vocab_tokens_.empty()) { - std::ostringstream oss; - oss << "No valid vocab tokens were found to build the trie."; - throw std::invalid_argument(oss.str()); - } - if (!continuing_subword_prefix.empty()) { - const bool suffix_token_exists = std::any_of( - failure_vocab_tokens_.begin(), - failure_vocab_tokens_.end(), - [](const FailureVocabToken& token) { return token.IsSuffixToken(); }); - if (!suffix_token_exists) { - auto new_suffix_token = continuing_subword_prefix + - std::string(1, utils::kInvalidControlChar); - failure_vocab_tokens_.emplace_back( - new_suffix_token, unk_id, continuing_subword_prefix); - } - } - if (with_pretokenization_) { - for (uint32_t cp = 1; cp <= 0x0010FFFF; ++cp) { - if (!utils::IsUnicodeChar(cp) || !utils::IsPunctuationOrChineseChar(cp)) { - continue; - } - char utf_str[5]; - utils::GetUTF8Str(reinterpret_cast(&cp), utf_str, 1); - std::string punc_str(utf_str); - if (vocab.count(punc_str) == 0) { - failure_vocab_tokens_.emplace_back( - punc_str, unk_id, continuing_subword_prefix); - } - } - failure_vocab_tokens_.emplace_back( - std::string(1, kInvalidControlChar), unk_id, continuing_subword_prefix); - } -} - -void FailureArray::CreateVocabFromFailureVocab( - const std::vector& failure_vocab_tokens, - std::unordered_map* vocab) const { - for (auto&& failure_vocab : failure_vocab_tokens) { - (*vocab)[failure_vocab.Token()] = failure_vocab.TokenId(); - } -} - -void FailureArray::InitFromVocabAndTrie( - const std::unordered_map& vocab, - Trie* trie, - const std::string& unk_token, - const std::string& continuing_subword_prefix) { - BuildFailureVocab(vocab, unk_token, continuing_subword_prefix); - - // Create Trie - std::unordered_map new_vocab; - CreateVocabFromFailureVocab(failure_vocab_tokens_, &new_vocab); - trie->SetVocab(new_vocab); - - // Create failure array - BuildFailureArray(failure_vocab_tokens_, trie); -} - -void FailureArray::RemovePunctuationTrieLink(Trie* trie) const { - auto continuing_subword_prefix = trie->GetContinuingSubwordPrefix(); - if (with_pretokenization_ && !continuing_subword_prefix.empty()) { - int cur_idx = 0; - int next_idx = 0; - uint32_t curr_char, next_char; - bool prev_node_is_root = false; - auto node = trie->CreateRootTraversalCursor(); - while (cur_idx < continuing_subword_prefix.length()) { - next_idx = cur_idx; - auto chwidth = utils::UTF8ToUInt32( - continuing_subword_prefix.data() + next_idx, &curr_char); - curr_char = utils::UTF8ToUnicode(curr_char); - next_idx = cur_idx + chwidth; - prev_node_is_root = (node.node_id_ == trie->kRootNodeId); - std::string cur_unicode_char(continuing_subword_prefix.data() + cur_idx, - chwidth); - if (!trie->TryTraverseSeveralSteps(&node, cur_unicode_char)) { - throw std::runtime_error( - "Cannot locate a character in suffix_indicator_. It should never " - "happen."); - } - if (IsPunctuationOrChineseChar(curr_char)) { - if (prev_node_is_root) { - cur_idx = next_idx; - auto next_chwidth = utils::UTF8ToUInt32( - continuing_subword_prefix.data() + next_idx, &next_char); - next_idx += next_chwidth; - std::string next_unicode_char( - continuing_subword_prefix.data() + cur_idx, next_chwidth); - auto child_node = node; - if (!trie->TryTraverseSeveralSteps(&child_node, next_unicode_char)) { - throw std::runtime_error( - "Cannot locate a character in suffix_indicator_. It should " - "never happen."); - } - trie->DeleteLinkFromParent(child_node.node_id_); - } else { - trie->DeleteLinkFromParent(node.node_id_); - } - break; - } - cur_idx = next_idx; - } - } -} - -// Algorithm 2 in https://arxiv.org/pdf/2012.15524.pdf -void FailureArray::BuildFailureArray( - const std::vector& failure_vocab_tokens, Trie* trie) { - std::vector> node_outgoing_edge_labels; - BuildOutgoingEdgeLabelsForTrie( - failure_vocab_tokens, trie, &node_outgoing_edge_labels); - failure_array_.resize(trie->Size()); - std::queue trie_node_queue({trie->kRootNodeId}); - if (trie->GetSuffixRoot() != trie->kRootNodeId) { - trie_node_queue.push(trie->GetSuffixRoot()); - } - while (!trie_node_queue.empty()) { - uint32_t parent_id = trie_node_queue.front(); - trie_node_queue.pop(); - std::vector outgoing_labels_sorted( - node_outgoing_edge_labels[parent_id].begin(), - node_outgoing_edge_labels[parent_id].end()); - std::sort(outgoing_labels_sorted.begin(), outgoing_labels_sorted.end()); - for (const char edge_label : outgoing_labels_sorted) { - auto child_node = trie->CreateTraversalCursor(parent_id); - if (!trie->TryTraverseOneStep(&child_node, edge_label)) { - std::ostringstream oss; - oss << "Failed to traverse to child following edge " << edge_label - << " at parent " << parent_id << "."; - throw std::runtime_error(oss.str()); - } - if (child_node.node_id_ == trie->GetSuffixRoot()) { - continue; - } - int child_data_value = -1; - // Case 1: str(v) in V - // * f(v) = trie.GetSuffixRoot() - // * F(v) = [str(v)] - if (trie->TryGetData(child_node, &child_data_value)) { - uint32_t failure_link = trie->GetSuffixRoot(); - if (node_id_is_punc_map_.count(child_node.node_id_) == 0) { - throw std::invalid_argument( - "Failed to find if an end node in the trie is a punctuation char " - "in node_id_is_punc_map_. It should never happen."); - } - if (with_pretokenization_ && - node_id_is_punc_map_.at(child_node.node_id_)) { - failure_link = trie->GetPuncFailureNode(); - } - AssignFailureLinkAndPops(child_node.node_id_, - failure_link, - {child_data_value}, - utils::kNullFailurePopsList); - trie_node_queue.push(child_node.node_id_); - continue; - } - - // Case 2: str(v) is not in V - const Failure& parent_failure = failure_array_[parent_id]; - if (parent_failure.failure_link_ != utils::kNullNode) { - std::vector one_step_pops; - auto curr_node = - trie->CreateTraversalCursor(parent_failure.failure_link_); - // Find the failure link util the failure link is root or - // the node has the outgoing label correspoding to edge_label. - while (true) { - if (trie->TryTraverseOneStep(&curr_node, edge_label)) { - AssignFailureLinkAndPops( - child_node.node_id_, - curr_node.node_id_, - one_step_pops, - parent_failure.failure_pops_offset_length_); - break; - } - const Failure& curr_node_failure = failure_array_[curr_node.node_id_]; - if (curr_node_failure.failure_link_ == utils::kNullNode) { - break; - } - GetFailurePopsAndAppendToOut( - curr_node_failure.failure_pops_offset_length_, &one_step_pops); - trie->SetTraversalCursor(&curr_node, curr_node_failure.failure_link_); - } - } - // If the failure_link of parent is root, - // * f(v) = none - // * F(v) = [] - trie_node_queue.push(child_node.node_id_); - } - } - RemovePunctuationTrieLink(trie); -} - -void FailureArray::AssignFailureLinkAndPops( - uint32_t cur_node, - uint32_t failure_link, - const std::vector& one_step_pops, - int parent_failure_pops_offset_length) { - if (failure_link == utils::kNullNode) { - return; - } - auto& curr_node_failure = failure_array_[cur_node]; - curr_node_failure.failure_link_ = failure_link; - if (one_step_pops.empty()) { - curr_node_failure.failure_pops_offset_length_ = - parent_failure_pops_offset_length; - } else { - const int offset = failure_pops_pool_.size(); - if (offset > utils::kMaxSupportedFailurePoolOffset) { - std::ostringstream oss; - oss << "Failure pops list offset is " << offset - << ", which exceeds maximum supported offset " - << utils::kMaxSupportedFailurePoolOffset - << ". The vocabulary seems to be too large to be supported."; - throw std::runtime_error(oss.str()); - } - GetFailurePopsAndAppendToOut(parent_failure_pops_offset_length, - &failure_pops_pool_); - failure_pops_pool_.insert( - failure_pops_pool_.end(), one_step_pops.begin(), one_step_pops.end()); - const int length = failure_pops_pool_.size() - offset; - if (length > utils::kMaxSupportedFailurePoolOffset) { - std::ostringstream oss; - oss << "Failure pops list size is " << length - << ", which exceeds maximum supported offset " - << utils::kMaxFailurePopsListSize; - throw std::runtime_error(oss.str()); - } - curr_node_failure.failure_pops_offset_length_ = - utils::EncodeFailurePopList(offset, length); - } -} - -void FailureArray::GetFailurePopsAndAppendToOut( - uint32_t failure_pops_offset_length, std::vector* out_failure_pops) { - if (failure_pops_offset_length == utils::kNullFailurePopsList) { - return; - } - int offset = 0, length = 0; - utils::GetFailurePopsOffsetAndLength( - failure_pops_offset_length, &offset, &length); - out_failure_pops->insert(out_failure_pops->end(), - failure_pops_pool_.begin() + offset, - failure_pops_pool_.begin() + offset + length); -} - -void FailureArray::BuildOutgoingEdgeLabelsForTrie( - const std::vector& failure_vocab_tokens, - Trie* trie, - std::vector>* node_outgoing_edge_labels) { - node_outgoing_edge_labels->resize(trie->Size()); - const std::string dummy_token = std::string(1, utils::kInvalidControlChar); - for (auto& item : failure_vocab_tokens) { - if (item.Token() != dummy_token) { - BuildOutgoingEdgeLabelsFromToken(item, trie, node_outgoing_edge_labels); - } - } -} - -void FailureArray::BuildOutgoingEdgeLabelsFromToken( - const FailureVocabToken& vocab_token, - Trie* trie, - std::vector>* node_outgoing_edge_labels) { - const std::string& token = vocab_token.Token(); - Trie::TraversalCursor curr_node; - int char_pos = 0; - trie->SetTraversalCursor(&curr_node, Trie::kRootNodeId); - while (char_pos < token.size()) { - const char edge_label = token[char_pos]; - (*node_outgoing_edge_labels)[curr_node.node_id_].insert(edge_label); - if (!trie->TryTraverseOneStep(&curr_node, edge_label)) { - std::ostringstream oss; - oss << "Error in traversing to child following edge `" << edge_label - << "` from the prefix `" << token.substr(0, char_pos) - << "` at parent id " << curr_node.node_id_ << ". The token is `" - << token << "`. The char position" - << " is " << char_pos << "."; - - throw std::runtime_error(oss.str()); - } - ++char_pos; - } - node_id_is_punc_map_[curr_node.node_id_] = - !vocab_token.IsSuffixToken() && vocab_token.ContainsPunctuation() && - vocab_token.TokenUnicodeLengthWithoutContinuingSubwordPrefix() == 1; -} - - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/failure.h b/fast_tokenizer/fast_tokenizer/utils/failure.h deleted file mode 100644 index c302f53496a8..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/failure.h +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2022 TF.Text Authors. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include -#include -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -class Trie; - -// Used in Fast WordPiece Model specially -struct Failure { - uint32_t failure_link_; - // Indicate the number of failure_pops - // and the offset in failure_pops_pool - uint32_t failure_pops_offset_length_; - Failure(); -}; - -class FailureVocabToken { -public: - FailureVocabToken(const std::string& token, - int token_id, - const std::string& continuing_subword_prefix); - - const std::string& Token() const; - - int TokenId() const; - bool IsSuffixToken() const; - bool ContainsPunctuation() const; - int TokenUnicodeLengthWithoutContinuingSubwordPrefix() const; - int TokenLengthWithoutContinuingSubwordPrefix() const; - -private: - std::string token_; - int token_id_; - bool is_suffix_token_; - int actual_token_start_offset_; - int actual_token_unicode_len_; - bool contains_punctuation_; -}; - -struct FailureArray { - FailureArray(bool with_pretokenization = false) - : with_pretokenization_(with_pretokenization) {} - void BuildFailureArray( - const std::vector& failure_vocab_tokens, Trie* trie); - void BuildFailureVocab(const std::unordered_map& vocab, - const std::string& unk_token, - const std::string& continuing_subword_prefix); - void InitFromVocabAndTrie( - const std::unordered_map& vocab, - Trie* trie, - const std::string& unk_token, - const std::string& continuing_subword_prefix); - const Failure* GetFailure(int idx) const { return &(failure_array_.at(idx)); } - int GetFailurePop(int idx) const { return failure_pops_pool_.at(idx); } - void SetWithPretokenization(bool with_pretokenization) { - with_pretokenization_ = with_pretokenization; - } - -private: - void BuildOutgoingEdgeLabelsForTrie( - const std::vector& failure_vocab_tokens, - Trie* trie, - std::vector>* node_outgoing_edge_labels); - void BuildOutgoingEdgeLabelsFromToken( - const FailureVocabToken& vocab_token, - Trie* trie, - std::vector>* node_outgoing_edge_labels); - void AssignFailureLinkAndPops(uint32_t cur_node, - uint32_t failure_link, - const std::vector& one_step_pops, - int parent_failure_pops_offset_length); - void GetFailurePopsAndAppendToOut(uint32_t failure_pops_offset_length, - std::vector* out_failure_pops); - void RemovePunctuationTrieLink(Trie* trie) const; - void CreateVocabFromFailureVocab( - const std::vector& failure_vocab_tokens, - std::unordered_map* vocab) const; - std::vector failure_array_; - std::vector failure_pops_pool_; - std::unordered_map node_id_is_punc_map_; - std::vector failure_vocab_tokens_; - bool with_pretokenization_; // The end-to-end version of FailureArray -}; - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/lattice.cc b/fast_tokenizer/fast_tokenizer/utils/lattice.cc deleted file mode 100644 index 14447788cda5..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/lattice.cc +++ /dev/null @@ -1,546 +0,0 @@ -// Copyright 2016 Google Inc. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "fast_tokenizer/utils/lattice.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "glog/logging.h" - -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -// Size of nodes pre-allocated in Lattice. -constexpr size_t kPreallocateLatticeNodeSize = 1024; - -constexpr float kEpsilon = 1e-7; - -constexpr unsigned int kDefaultSeed = static_cast(-1); -static std::atomic g_seed(kDefaultSeed); - -inline float LogSumExp(float x, float y, bool init_mode) { - if (init_mode) { - return y; - } - const float vmin = std::min(x, y); - const float vmax = std::max(x, y); - constexpr float kMinusLogEpsilon = 50; - if (vmax > vmin + kMinusLogEpsilon) { - return vmax; - } else { - return vmax + log(std::exp(static_cast(vmin - vmax)) + 1.0); - } -} - -uint32_t GetRandomGeneratorSeed() { - return g_seed == kDefaultSeed ? std::random_device{}() : g_seed.load(); -} - -std::mt19937 *GetRandomGenerator() { - thread_local static std::mt19937 mt(GetRandomGeneratorSeed()); - return &mt; -} - -inline float Gumbel() { - const float kEpsilon = 1e-7; - auto *mt = GetRandomGenerator(); - std::uniform_real_distribution dis(0.0, 1.0); - float noise = -std::log(-(std::log(dis(*mt) + kEpsilon))); - return noise; -} - -Lattice::Lattice() : node_allocator_(kPreallocateLatticeNodeSize) {} -Lattice::~Lattice() {} - -const std::vector &Lattice::begin_nodes(int pos) const { - return begin_nodes_[pos]; -} - -const std::vector &Lattice::end_nodes(int pos) const { - return end_nodes_[pos]; -} - -int Lattice::size() const { - // -1 because surface_ may include the EOS. - return std::max(0, surface_.size() - 1); -} - -int Lattice::utf8_size() const { return sentence_.size(); } - -const char *Lattice::sentence() const { return sentence_.data(); } - -const char *Lattice::surface(int pos) const { return surface_[pos]; } - -Lattice::Node *Lattice::bos_node() const { return end_nodes_[0][0]; } - -Lattice::Node *Lattice::eos_node() const { return begin_nodes_[size()][0]; } - -Lattice::Node *Lattice::NewNode() { - Node *node = node_allocator_.Allocate(); - node->node_id = node_allocator_.size() - 1; - return node; -} - -void Lattice::Clear() { - begin_nodes_.clear(); - end_nodes_.clear(); - sentence_ = utils::simple_string_view(""); - surface_.clear(); - node_allocator_.Free(); -} - -void Lattice::SetSentence(utils::simple_string_view sentence) { - Clear(); - - sentence_ = sentence; - surface_.reserve(sentence.size() + 1); - - while (!sentence.empty()) { - const int mblen = - std::min(utils::OneCharLen(sentence.data()), sentence.size()); - surface_.push_back(sentence.data()); - sentence.remove_prefix(mblen); - } - surface_.push_back(sentence.data()); - - const int len = size(); - begin_nodes_.resize(len + 1); - end_nodes_.resize(len + 1); - - constexpr size_t kReservedNodeSize = 16; - for (int i = 0; i <= len; ++i) { - begin_nodes_[i].reserve(kReservedNodeSize); - end_nodes_[i].reserve(kReservedNodeSize); - } - - Node *bos = NewNode(); - bos->id = -1; - bos->pos = 0; - end_nodes_[0].push_back(bos); - - Node *eos = NewNode(); - eos->id = -1; - eos->pos = len; - begin_nodes_[len].push_back(eos); -} - -Lattice::Node *Lattice::Insert(int pos, int length) { - Node *node = NewNode(); - node->pos = pos; - node->length = length; - const int utf8_length = - static_cast(surface(pos + length) - surface(pos)); - node->piece = simple_string_view(surface(pos), utf8_length); - begin_nodes_[pos].push_back(node); - end_nodes_[pos + node->length].push_back(node); - return node; -} - -Lattice::LatticePathWithScore Lattice::Viterbi() { - const int len = size(); - - for (int pos = 0; pos <= len; ++pos) { - for (Node *rnode : begin_nodes_[pos]) { - rnode->prev = nullptr; - float best_score = 0.0; - Node *best_node = nullptr; - for (Node *lnode : end_nodes_[pos]) { - const float score = lnode->backtrace_score + rnode->score; - if (best_node == nullptr || score > best_score) { - best_node = lnode; - best_score = score; - } - } - if (best_node == nullptr) { - LOG(ERROR) << "Failed to find the best path in Viterbi."; - return {}; - } - rnode->prev = best_node; - rnode->backtrace_score = best_score; - } - } - - // backtrace - std::vector results; - float score = begin_nodes(len)[0]->backtrace_score; - for (Node *node = begin_nodes_[len][0]->prev; node->prev != nullptr; - node = node->prev) { - results.push_back(node); - } - - std::reverse(results.begin(), results.end()); - - LatticePathWithScore retval = {results, score}; - - return retval; -} - -std::vector Lattice::ForwardAlgorithm(float inv_theta) const { - const int len = size(); - std::vector alpha(node_allocator_.size(), 0.0); - - for (int pos = 0; pos <= len; ++pos) { - for (Node *rnode : begin_nodes_[pos]) { - for (Node *lnode : end_nodes_[pos]) { - alpha[rnode->node_id] = - LogSumExp(alpha[rnode->node_id], - inv_theta * lnode->score + alpha[lnode->node_id], - lnode == end_nodes_[pos][0]); - } - } - } - - return alpha; -} - -std::vector Lattice::BackwardAlgorithm(float inv_theta) const { - const int len = size(); - std::vector beta(node_allocator_.size(), 0.0); - - for (int pos = len; pos >= 0; --pos) { - for (Node *lnode : end_nodes_[pos]) { - for (Node *rnode : begin_nodes_[pos]) { - beta[lnode->node_id] = LogSumExp(beta[lnode->node_id], - rnode->score + beta[rnode->node_id], - rnode == begin_nodes_[pos][0]); - } - } - } - - return beta; -} - -float Lattice::PopulateMarginal(float freq, - std::vector *expected) const { - if (expected == nullptr) return 0.0; - - const int len = size(); - - // alpha and beta (accumulative log prob) in Forward Backward. - // the index of alpha/beta is Node::node_id. - - const auto alpha = ForwardAlgorithm(1.0); - const auto beta = BackwardAlgorithm(1.0); - - const float Z = alpha[begin_nodes_[len][0]->node_id]; - for (int pos = 0; pos < len; ++pos) { - for (Node *node : begin_nodes_[pos]) { - if (node->id >= 0) { - // the index of |expected| is a Node::id, which is a vocabulary id. - (*expected)[node->id] += - freq * - std::exp(static_cast(alpha[node->node_id] + node->score + - beta[node->node_id] - Z)); - } - } - } - - return freq * Z; -} - -float Lattice::CalculateEntropy(float inv_theta) const { - const int len = size(); - - // alpha[node_id] is the marginal prob of sequence up to start of node - // H is entropy of sequence - // the index of alpha/H is Node::node_id. - std::vector H(node_allocator_.size(), 0.0); - - // Populate the forward marginals to get the normalising constant - const auto alpha = ForwardAlgorithm(inv_theta); - - // Now populate the forward entropies - for (int pos = 0; pos <= len; ++pos) { - for (Node *rnode : begin_nodes_[pos]) { - for (Node *lnode : end_nodes_[pos]) { - // Contribution each lnode makes = p(lnode) * (H(lnode) + log p(lnode)) - - // We have to normalise p(lnode) by the marginal contribution it makes - const float lnode_transition_prob = - ((inv_theta * lnode->score) + alpha[lnode->node_id] - - alpha[rnode->node_id]); - H[rnode->node_id] += std::exp(lnode_transition_prob) * - (H[lnode->node_id] + lnode_transition_prob); - } - } - } - - return -H[begin_nodes_[len][0]->node_id]; -} - -// The node structure to support A* algorithm in Lattice::NBest() -struct Hypothesis { - Lattice::Node *node; - Hypothesis *next; - float fx; // the priority to pop a new hypothesis from the priority queue. - float gx; // the sum of scores from EOS to the left-most node in x. -}; - -// Helper function for cloning a Hypothesis and the ones on their next paths. -// The graph structure is preserved. -// -// to_clone: the Hypothesis to clone. -// clone_map: mapping between the old pointers and the new pointers. -// allocator: allocate and own the cloned Hypothesis. -// -// Returns the cloned Hypothesis*. All Hypothesis on its "next" chain are also -// guaranteed to have been allocated via "allocator", and "clone_map" is updated -// with all new mappings. -Hypothesis *CloneHypAndDependents( - const Hypothesis *to_clone, - std::unordered_map *clone_map, - FreeList *allocator) { - Hypothesis *cloned = nullptr; - Hypothesis **result_callback = &cloned; - - // Iteratively clone "to_clone" and its dependencies. - // The new pointer will be written back to *result_callback. - while (to_clone != nullptr) { - // If "to_clone" has already been cloned before, we just look up the result. - auto iter = clone_map->find(to_clone); - if (iter != clone_map->end()) { - *result_callback = iter->second; - break; - } - - // Allocate a new Hypothesis and copy the values. - Hypothesis *new_hyp = allocator->Allocate(); - *new_hyp = *to_clone; - *result_callback = new_hyp; - clone_map->insert({to_clone, new_hyp}); - - // Move on to clone "to_clone->next". - to_clone = to_clone->next; - result_callback = &(new_hyp->next); - LOG(ERROR) << "Failed to find the best path in Viterbi."; - } - return cloned; -} - -std::vector Lattice::NBest(size_t nbest_size, - bool sample, - float inv_theta) { - if (nbest_size < 1) { - LOG(WARNING) << "nbest_size >= 1. Returns empty result."; - return {}; - } - - if (nbest_size == 1 && !sample) { - return {Viterbi()}; - } - - // Uses A* search to enumerate N-bests. - // Given a lattice, enumerates hypotheses (paths) from EOS. - // At each partial path x, compute f(x) as follows - // f(x) = g(x) + h(x). - // g(x): the sum of scores from EOS to the left-most node in x. - // for a complete hypothesis, g(hyp) is the score of the hypothesis. - // h(x): a heuristic that estimates the largest score from x to BOS. - // f(x): the priority to pop a new hypothesis from the priority queue. - // - // As left-to-right Viterbi search can tell the *exact* value of h(x), - // we can obtain the exact n-best results with A*. - - class HypothesisComparator { - public: - const bool operator()(Hypothesis *h1, Hypothesis *h2) { - return (h1->fx < h2->fx); - } - }; - - using Agenda = std::priority_queue, - HypothesisComparator>; - constexpr size_t kPreallocatedHypothesisSize = 512; - FreeList hypothesis_allocator(kPreallocatedHypothesisSize); - - Agenda agenda; - std::vector results; - - auto *eos = hypothesis_allocator.Allocate(); - eos->node = eos_node(); - eos->next = nullptr; - eos->gx = 0.0; - - std::vector alpha(node_allocator_.size(), 0.0); - - if (sample) { - // Run forwards algorithm to get normalising constants - alpha = ForwardAlgorithm(inv_theta); - // f(eos) = Gumbel(0), as it is the perturbed score of the entire lattice. - eos->fx = Gumbel(); - } else { - // Run Viterbi first to fill backtrace score. - Viterbi(); - eos->fx = eos->node->backtrace_score; - } - agenda.push(eos); - - int shrink_count = 0; // Number of times agenda has shrunk. For logging only. - bool printed_memory_warning = false; // For logging only. - while (!agenda.empty()) { - auto *top = agenda.top(); - agenda.pop(); - auto *node = top->node; - - // Reaches to BOS - if (node == bos_node()) { - results.resize(results.size() + 1); - for (auto *n = top->next; n->next != nullptr; n = n->next) { - results.back().first.push_back(n->node); - } - results.back().second = top->fx; - if (results.size() == nbest_size) { - break; - } - continue; - } - - const int end_nodes_size = end_nodes(node->pos).size(); - std::vector probs(end_nodes_size, 0.0); - std::vector perturbed_probs(end_nodes_size, 0.0); - std::vector adjusted_probs(end_nodes_size, 0.0); - const float Z = alpha[node->node_id]; - if (sample) { - float max_score = -1e8; - // Calculate the marginal and perturbed scores for stochastic search - for (int i = 0; i < end_nodes(node->pos).size(); i++) { - Node *lnode = end_nodes(node->pos)[i]; - // Calculate backwards transition score - probs[i] = - top->gx + alpha[lnode->node_id] + (inv_theta * lnode->score) - Z; - perturbed_probs[i] = probs[i] + Gumbel(); - if (perturbed_probs[i] > max_score) { - max_score = perturbed_probs[i]; - } - } - // Now constrain the sampled continuations to match the score of parent - for (int i = 0; i < adjusted_probs.size(); i++) { - // Use numerically stable version of truncated Gumbel: - // https://arxiv.org/pdf/1903.06059.pdf appendix B.3 - const float v = top->fx - perturbed_probs[i] + - std::log1p(-std::exp(perturbed_probs[i] - max_score)); - adjusted_probs[i] = top->fx - std::max(static_cast(0.0), v) - - std::log1p(std::exp(-std::abs(v))); - } - } - - // Expands new node ending at node->pos - for (int i = 0; i < end_nodes(node->pos).size(); i++) { - Node *lnode = end_nodes(node->pos)[i]; - auto *hyp = hypothesis_allocator.Allocate(); - hyp->node = lnode; - if (sample) { - hyp->gx = probs[i]; - hyp->fx = adjusted_probs[i]; - } else { - hyp->gx = lnode->score + top->gx; // just adds node->score - hyp->fx = - lnode->backtrace_score + top->gx; // backtrace_score is h(node). - } - hyp->next = top; - agenda.push(hyp); - } - - static constexpr int kOneBillion = 1000000000; // 10^9. - if (hypothesis_allocator.size() >= kOneBillion) { - if (!printed_memory_warning) { - printed_memory_warning = true; - LOG(WARNING) << "Allocator size exceeds " << kOneBillion - << " with an example of length " << this->size(); - } - } - - // When the input is too long or contains duplicated phrases, - // `agenda` will get extremely big. Here we avoid this case by - // dynamically shrinking the agenda. - constexpr int kMaxAgendaSize = 10000; - constexpr int kMinAgendaSize = 512; - if (agenda.size() >= kMaxAgendaSize) { - // Keeps the top `kMinAgendaSize` hypothesis. - Agenda new_agenda; - // Keeps the top hypothesis and the ones on their "next" paths. - FreeList new_allocator(kPreallocatedHypothesisSize); - // Map between old Hypothesis* and new Hypothesis*. - std::unordered_map clone_map; - - const int size = std::min(kMinAgendaSize, nbest_size * 10); - shrink_count++; - LOG(WARNING) << "Too big agenda size " << agenda.size() - << ". Shrinking (round " << shrink_count << ") down to " - << size << "."; - for (int i = 0; i < size; ++i) { - const Hypothesis *top_hyp = agenda.top(); - Hypothesis *cloned_hyp = - CloneHypAndDependents(top_hyp, &clone_map, &new_allocator); - new_agenda.push(cloned_hyp); - agenda.pop(); - } - agenda = std::move(new_agenda); - hypothesis_allocator.swap(new_allocator); - } - } - - return results; -} - -std::vector Lattice::Sample(float inv_theta) { - const int len = size(); - if (len == 0) return {}; - - std::vector alpha(node_allocator_.size(), 0.0); - - alpha = ForwardAlgorithm(inv_theta); - - auto *mt = GetRandomGenerator(); - - std::vector results; - std::vector probs; - - float Z = alpha[eos_node()->node_id]; - Node *node = eos_node(); - while (true) { - probs.clear(); - for (const Node *lnode : end_nodes_[node->pos]) { - probs.push_back(std::exp(static_cast( - alpha[lnode->node_id] + inv_theta * lnode->score - Z))); - } - std::discrete_distribution dist(probs.begin(), probs.end()); - node = end_nodes_[node->pos][dist(*mt)]; - if (node == bos_node()) break; - - Z = alpha[node->node_id]; - results.push_back(node); - } - - std::reverse(results.begin(), results.end()); - return results; -} - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/lattice.h b/fast_tokenizer/fast_tokenizer/utils/lattice.h deleted file mode 100644 index daa6523d059d..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/lattice.h +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2016 Google Inc. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include "fast_tokenizer/utils/string_view.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -// Copy from https://github.com/google/sentencepiece/blob/master/src/freelist.h -// Simple FreeList that allocates a chunk of T at once. -template -class FreeList { -public: - FreeList() = delete; - explicit FreeList(size_t chunk_size) : chunk_size_(chunk_size) {} - virtual ~FreeList() { - for (auto &chunk : freelist_) delete[] chunk; - } - - // `Free` doesn't free the object but reuse the allocated memory chunks. - void Free() { - const int size = std::min(chunk_index_ + 1, freelist_.size()); - for (int i = 0; i < size; ++i) { - T *chunk = freelist_[i]; - memset(static_cast(chunk), 0, sizeof(*chunk) * chunk_size_); - } - chunk_index_ = 0; - element_index_ = 0; - } - - // Returns the number of allocated elements. - size_t size() const { return chunk_size_ * chunk_index_ + element_index_; } - - void swap(FreeList &other) { - std::swap(freelist_, other.freelist_); - std::swap(element_index_, other.element_index_); - std::swap(chunk_index_, other.chunk_index_); - std::swap(chunk_size_, other.chunk_size_); - } - - // Returns the element as an array. - T *operator[](size_t index) const { - return freelist_[index / chunk_size_] + index % chunk_size_; - } - - // Allocates new element. - T *Allocate() { - if (element_index_ >= chunk_size_) { - ++chunk_index_; - element_index_ = 0; - } - - if (chunk_index_ == freelist_.size()) { - T *chunk = new T[chunk_size_]; - memset(static_cast(chunk), 0, sizeof(*chunk) * chunk_size_); - freelist_.push_back(chunk); - } - - T *result = freelist_[chunk_index_] + element_index_; - ++element_index_; - - return result; - } - -private: - std::vector freelist_; - - // The last element is stored at freelist_[chunk_index_][element_index_] - size_t element_index_ = 0; - size_t chunk_index_ = 0; - size_t chunk_size_ = 0; // Do not modify except in swap() -}; - - -// Copy from -// https://github.com/google/sentencepiece/blob/master/src/unigram_model.h -class Lattice { -public: - Lattice(); - virtual ~Lattice(); - - struct Node { - utils::simple_string_view piece; // Sentence piece representation. - uint32_t pos; // Unicode position in the sentence. - uint32_t length; // Unicode length, not UT8 byte. - uint32_t node_id; // unique id in the current lattice. - int id; // vocab id. (maybe -1 for UNK) - float score; // logprob of this sentencepiece. - float backtrace_score; // backtrace info used in Viterbi. - Node *prev; // best previous node on Viterbi path. - - std::string DebugString() const; - }; - - // Returns bos node. - Node *bos_node() const; - - // Returns eos node. - Node *eos_node() const; - - // Returns nodes starting at |pos|. - const std::vector &begin_nodes(int pos) const; - - // Returns nodes ending at |pos|. - const std::vector &end_nodes(int pos) const; - - // Returns Unicode character length. - int size() const; - - // Returns multi-byte (utf8) length. - int utf8_size() const; - - // Returns the substring of sentence. sentence[pos:] - const char *surface(int pos) const; - - // Returns immutable sentence. The same as surface(0) - const char *sentence() const; - - // Clears the lattice. - void Clear(); - - // Sets new sentence. - void SetSentence(utils::simple_string_view sentence); - - // Inserts a new node at [pos, pos + length - 1]. - // After calling this method, The caller must set Node::score and Node::id. - Node *Insert(int pos, int length); - - using LatticePathWithScore = std::pair, float>; - - // Returns Viterbi path. All nodes must be populated in advance. - LatticePathWithScore Viterbi(); - - // Runs forwards/backwards algorithm, returns vector with normalised - // transition probs. - std::vector ForwardAlgorithm(float theta) const; - std::vector BackwardAlgorithm(float theta) const; - - // Returns n-best results. - std::vector NBest(size_t nbest_size, - bool sample, - float theta); - - // Samples one path from the lattice according to the - // generation probability (Product of piece probabilities). - // `theta` is a smoothing parameter. - std::vector Sample(float theta); - - // Calculates the entropy of the lattice. - float CalculateEntropy(float theta) const; - - // Populates marginal probability of every node in this lattice. - // |freq| is the frequency of the sentence. - // for (auto *node : all_nodes_) { - // (*expected)[node->id] += marginal_prob_of_node * freq; - // } - // Returns the log-likelihood of this sentence. - float PopulateMarginal(float freq, std::vector *expected) const; - -private: - // Returns new node. - // Lattice class has the ownership of the returned value. - Node *NewNode(); - - utils::simple_string_view sentence_; - std::vector surface_; - std::vector> begin_nodes_; - std::vector> end_nodes_; - FreeList node_allocator_; -}; - - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/path.h b/fast_tokenizer/fast_tokenizer/utils/path.h deleted file mode 100644 index a58a00af613a..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/path.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include - -#ifdef _MSC_VER -#define PATH_SEP "\\" -#else -#define PATH_SEP "/" -#endif - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -inline std::string PathJoin(const std::vector& paths, - const std::string& sep = PATH_SEP) { - if (paths.size() == 1) { - return paths[0]; - } - std::string filepath = ""; - for (const auto& path : paths) { - if (filepath == "") { - filepath += path; - continue; - } - if (path[0] == sep[0] || filepath.back() == sep[0]) { - filepath += path; - } else { - filepath += sep + path; - } - } - return filepath; -} - -inline std::string PathJoin(const std::string& folder, - const std::string filename, - const std::string& sep = PATH_SEP) { - return PathJoin(std::vector{folder, filename}, sep); -} - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/sentencepiece_normalizer.cc b/fast_tokenizer/fast_tokenizer/utils/sentencepiece_normalizer.cc deleted file mode 100644 index 4a7bf9950ab5..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/sentencepiece_normalizer.cc +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2016 Google Inc. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "fast_tokenizer/utils/sentencepiece_normalizer.h" -#include -#include "fast_tokenizer/utils/unique_ptr.h" -#include "fast_tokenizer/utils/utf8.h" -#include "fast_tokenizer/utils/utils.h" - -#include "glog/logging.h" -#include "unicode/brkiter.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -PrefixMatcher::PrefixMatcher(const std::set& dic) { - if (dic.empty()) return; - std::vector key; - key.reserve(dic.size()); - for (const auto& it : dic) key.push_back(it); - trie_ = utils::make_unique(); - trie_->build(key.size(), const_cast(&key[0]), nullptr, nullptr); -} - -int PrefixMatcher::PrefixMatch(const char* w, size_t w_len, bool* found) const { - if (trie_ == nullptr) { - if (found) *found = false; - return std::min(w_len, OneCharLen(w)); - } - constexpr int kResultSize = 64; - Darts::DoubleArray::result_pair_type trie_results[kResultSize]; - const int num_nodes = - trie_->commonPrefixSearch(w, trie_results, kResultSize, w_len); - if (found) *found = (num_nodes > 0); - if (num_nodes == 0) { - return std::min(w_len, OneCharLen(w)); - } - - int mblen = 0; - for (int i = 0; i < num_nodes; ++i) { - mblen = std::max(trie_results[i].length, mblen); - } - return mblen; -} - -std::string PrefixMatcher::GlobalReplace(const char* w, - size_t w_len, - const char* out, - size_t out_len, - const char** result_w) const { - std::string result; - if (w_len > 0) { - bool found = false; - const int mblen = PrefixMatch(w, w_len, &found); - if (found) { - result.append(out, out_len); - } else { - result.append(w, mblen); - } - *result_w = w + mblen; - } - return result; -} - -Normalizer::Normalizer(const std::string& precompiled_charsmap) - : precompiled_charsmap_(precompiled_charsmap) { - Init(); -} - -Normalizer::Normalizer(const Normalizer& other) - : precompiled_charsmap_(other.precompiled_charsmap_) { - Init(); -} - - -Normalizer::~Normalizer() {} - -std::string Normalizer::GetPrecompiledCharsmap() const { - return precompiled_charsmap_; -} - -void Normalizer::Init() { - if (!precompiled_charsmap_.empty()) { -#ifdef IS_BIG_ENDIAN - DecodePrecompiledCharsMap(precompiled_charsmap_.data(), - precompiled_charsmap_.length(), - &trie_blob_, - &normalized_blob_, - &precompiled_charsmap_buffer_); -#else - DecodePrecompiledCharsMap(precompiled_charsmap_.data(), - precompiled_charsmap_.length(), - &trie_blob_, - &normalized_blob_); -#endif - // Reads the body of double array. - trie_ = utils::make_unique(); - // The second arg of set_array is not the size of blob, - // but the number of double array units. - trie_->set_array(const_cast(trie_blob_.data()), - trie_blob_.size() / trie_->unit_size()); - normalized_ = normalized_blob_.data(); - } -} - -void Normalizer::DecodePrecompiledCharsMap(const char* blob, - size_t blob_size, - std::string* trie_blob, - std::string* normalized, - std::string* buffer) { - uint32_t trie_blob_size = 0; - uint32_t offset = 0; - if (blob_size <= sizeof(trie_blob_size) || - !DecodePOD(blob, sizeof(trie_blob_size), &trie_blob_size) || - trie_blob_size >= blob_size) { - throw std::runtime_error("Blob for normalization rule is broken."); - } -#ifdef IS_BIG_ENDIAN - trie_blob_size = util::Swap32(trie_blob_size); -#endif - if (trie_blob_size >= blob_size) { - throw std::runtime_error("Trie data size exceeds the input blob size."); - } - offset += sizeof(trie_blob_size); -#ifdef IS_BIG_ENDIAN - buffer->assign(blob + offset, trie_blob_size); - uint32* data = reinterpret_cast(const_cast(buffer->data())); - for (int i = 0; i < trie_blob_size / 4; ++i) data[i] = util::Swap32(data[i]); - *trie_blob = std::string(buffer->data(), trie_blob_size); -#else - *trie_blob = std::string(blob + offset, trie_blob_size); -#endif - offset += trie_blob_size; - *normalized = std::string(blob + offset, blob_size - offset); -} - -std::string Normalizer::EncodePrecompiledCharsMap( - const std::string& trie_blob, const std::string& normalized) { - // - std::string blob; - blob.append(EncodePOD(trie_blob.size())); - blob.append(trie_blob.data(), trie_blob.size()); - blob.append(normalized.data(), normalized.size()); - -#ifdef IS_BIG_ENDIAN - uint32* data = reinterpret_cast(const_cast(blob.data())); - for (int i = 0; i <= trie_blob.size() / 4; ++i) { - data[i] = util::Swap32(data[i]); - } -#endif - return blob; -} - -std::pair Normalizer::NormalizePrefix( - const char* input, size_t input_len) const { - std::pair result; - if (input_len == 0) { - return result; - } - if (matcher_ != nullptr) { - bool found = false; - const int mblen = matcher_->PrefixMatch(input, input_len, &found); - if (found) { - return std::make_pair(simple_string_view(input, input_len), mblen); - } - } - size_t longest_length = 0; - int longest_value = 0; - if (trie_ != nullptr) { - // Allocates trie_results in stack, which makes the encoding speed 36% - // fast. (38k sentences/sec => 60k sentences/sec). Builder checks that the - // result size never exceeds kMaxTrieResultsSize. This array consumes - // 0.5kByte in stack, which is less than default stack frames (16kByte). - Darts::DoubleArray::result_pair_type - trie_results[Normalizer::kMaxTrieResultsSize]; - const size_t num_nodes = trie_->commonPrefixSearch( - input, trie_results, Normalizer::kMaxTrieResultsSize, input_len); - - // Finds the longest rule. - for (size_t k = 0; k < num_nodes; ++k) { - if (longest_length == 0 || trie_results[k].length > longest_length) { - longest_length = trie_results[k].length; // length of prefix - longest_value = trie_results[k].value; // pointer to |normalized_|. - } - } - } - - if (longest_length == 0) { - size_t length = 0; - if (!IsValidDecodeUTF8(input, input + input_len, &length)) { - // Found a malformed utf8. - // The rune is set to be 0xFFFD (REPLACEMENT CHARACTER), - // which is a valid Unicode of three bytes in utf8, - // but here we only consume one byte. - result.second = 1; - static const char kReplacementChar[] = "\xEF\xBF\xBD"; - result.first = simple_string_view(kReplacementChar); - } else { - result.second = length; - result.first = simple_string_view(input, length); - } - } else { - result.second = longest_length; - // No need to pass the size of normalized sentence, - // since |normalized| is delimitered by "\0". - result.first = simple_string_view(&(normalized_[longest_value])); - } - return result; -} - -bool Normalizer::Normalize(const char* input, - size_t input_len, - std::string* normalized, - std::vector* norm_to_orig, - std::u32string* u32content) const { - bool modified = false; - norm_to_orig->clear(); - normalized->clear(); - if (input_len == 0) { - return modified; - } - - // Reserves the output buffer to avoid re-allocations. - const size_t kReservedSize = input_len * 3; - normalized->reserve(kReservedSize); - norm_to_orig->reserve(kReservedSize); - if (u32content != nullptr) { - u32content->reserve(kReservedSize); - } - UErrorCode err = U_ZERO_ERROR; - std::unique_ptr iter( - icu::BreakIterator::createCharacterInstance(icu::Locale::getDefault(), - err)); - UText utext = UTEXT_INITIALIZER; - utext_openUTF8(&utext, input, input_len, &err); - iter->setText(&utext, err); - int curr_pos = iter->current(); - while (iter->next() != icu::BreakIterator::DONE) { - int next_pos = iter->current(); - int curr_len = next_pos - curr_pos; - std::pair p; - if (curr_len < 6) { - p = NormalizePrefix(input + curr_pos, curr_len); - simple_string_view sp = p.first; - if (sp.data() != input + curr_pos) { - if (!sp.empty()) { - for (size_t n = 0; n < sp.size(); ++n) { - *normalized += sp.data()[n]; - } - } - Replace(sp, - simple_string_view(input + curr_pos, curr_len), - norm_to_orig, - u32content); - modified = true; - curr_pos = next_pos; - continue; - } - } - int curr_grapheme_pos = curr_pos; - while (curr_grapheme_pos < next_pos) { - uint32_t content_char; - auto content_char_width = - utils::UTF8ToUInt32(input + curr_grapheme_pos, &content_char); - content_char = utils::UTF8ToUnicode(content_char); - p = NormalizePrefix(input + curr_grapheme_pos, content_char_width); - simple_string_view sp = p.first; - if (sp.data() != input + curr_grapheme_pos) { - if (!sp.empty()) { - for (size_t n = 0; n < sp.size(); ++n) { - *normalized += sp.data()[n]; - } - } - Replace( - sp, - simple_string_view(input + curr_grapheme_pos, content_char_width), - norm_to_orig, - u32content); - modified = true; - } else { - for (int i = 0; i < sp.size(); ++i) { - *normalized += sp.data()[i]; - } - if (u32content != nullptr) { - u32content->push_back(content_char); - } - norm_to_orig->push_back(0); - } - curr_grapheme_pos += content_char_width; - } - curr_pos = next_pos; - } - utext_close(&utext); - return modified; -} - -void Normalizer::Replace(const simple_string_view& new_part, - const simple_string_view& old_part, - std::vector* changes, - std::u32string* u32content) const { - auto new_unicode_len = - GetUnicodeLenFromUTF8(new_part.data(), new_part.size()); - auto old_unicode_len = - GetUnicodeLenFromUTF8(old_part.data(), old_part.size()); - if (u32content != nullptr) { - size_t utf8_len = 0; - while (utf8_len < new_part.size()) { - uint32_t content_char; - auto content_char_width = - utils::UTF8ToUInt32(new_part.data() + utf8_len, &content_char); - content_char = utils::UTF8ToUnicode(content_char); - u32content->push_back(content_char); - utf8_len += content_char_width; - } - } - changes->insert(changes->end(), new_unicode_len, 0); - if (new_unicode_len > old_unicode_len) { - auto diff = new_unicode_len - old_unicode_len; - for (auto i = changes->size() - 1; i >= changes->size() - diff; --i) { - (*changes)[i] = 1; - } - } else { - changes->back() -= old_unicode_len - new_unicode_len; - } -} - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/sentencepiece_normalizer.h b/fast_tokenizer/fast_tokenizer/utils/sentencepiece_normalizer.h deleted file mode 100644 index 3a8543cc39c8..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/sentencepiece_normalizer.h +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include -#include -#include - -#include "fast_tokenizer/utils/string_view.h" - -#include "darts.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -struct Cstrless { - bool operator()(const char* a, const char* b) const { - return std::strcmp(a, b) < 0; - } -}; - -class PrefixMatcher { -public: - // Initializes the PrefixMatcher with `dic`. - explicit PrefixMatcher(const std::set& dic); - - int PrefixMatch(const char* w, size_t w_len, bool* found = nullptr) const; - - std::string GlobalReplace(const char* w, - size_t w_len, - const char* out, - size_t out_len, - const char** result_w) const; - -private: - std::unique_ptr trie_; -}; - -class Normalizer { -public: - // Instantiates Normalizer with |spec|. - // |spec| should not be deleted until Normalizer is destroyed. - explicit Normalizer(const std::string& precompiled_charsmap); - Normalizer(const Normalizer& other); - virtual ~Normalizer(); - - virtual void SetPrefixMatcher(const PrefixMatcher* matcher) { - matcher_ = matcher; - } - - virtual bool Normalize(const char* input, - size_t input_len, - std::string* normalized, - std::vector* norm_to_orig, - std::u32string* u32content = nullptr) const; - std::string GetPrecompiledCharsmap() const; - -private: - void Init(); - void Replace(const simple_string_view& new_part, - const simple_string_view& old_part, - std::vector* changes, - std::u32string* u32content = nullptr) const; - std::pair NormalizePrefix(const char* input, - size_t input_len) const; - - - // // Encodes trie_blob and normalized string and return compiled blob. - static std::string EncodePrecompiledCharsMap(const std::string& trie_blob, - const std::string& normalized); - - // Decodes blob into trie_blob and normalized string. - static void DecodePrecompiledCharsMap(const char* blob, - size_t blob_size, - std::string* trie_blob, - std::string* normalized, - std::string* buffer = nullptr); - - static constexpr int kMaxTrieResultsSize = 32; - - std::unique_ptr trie_; - - const char* normalized_ = nullptr; - std::string normalized_blob_; - std::string trie_blob_; - - // Prefix matcher; - const PrefixMatcher* matcher_ = nullptr; - - // Split hello world into "hello_" and "world_" instead of - // "_hello" and "_world". - const bool treat_whitespace_as_suffix_ = false; - std::string precompiled_charsmap_buffer_; - std::string precompiled_charsmap_; -}; - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/shared_mutex.h b/fast_tokenizer/fast_tokenizer/utils/shared_mutex.h deleted file mode 100644 index 37931ff9f740..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/shared_mutex.h +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -// The code is from http://howardhinnant.github.io/shared_mutex.cpp -// C++ 11 shared_mutex implementation -class shared_mutex { - typedef std::mutex mutex_t; - typedef std::condition_variable cond_t; - typedef unsigned count_t; - - mutex_t mut_; - cond_t gate1_; - cond_t gate2_; - count_t state_; - - static const count_t write_entered_ = 1U << (sizeof(count_t) * CHAR_BIT - 1); - static const count_t n_readers_ = ~write_entered_; - -public: - shared_mutex() : state_(0) {} - ~shared_mutex() { std::lock_guard _(mut_); } - - shared_mutex(const shared_mutex&) = delete; - shared_mutex& operator=(const shared_mutex&) = delete; - - // Exclusive ownership - - void lock() { - std::unique_lock lk(mut_); - while (state_ & write_entered_) gate1_.wait(lk); - state_ |= write_entered_; - while (state_ & n_readers_) gate2_.wait(lk); - } - bool try_lock() { - std::unique_lock lk(mut_); - if (state_ == 0) { - state_ = write_entered_; - return true; - } - return false; - } - template - bool try_lock_for(const std::chrono::duration& rel_time) { - return try_lock_until(std::chrono::steady_clock::now() + rel_time); - } - template - bool try_lock_until(const std::chrono::time_point& abs_time); - void unlock() { - std::lock_guard _(mut_); - state_ = 0; - gate1_.notify_all(); - } - - // Shared ownership - - void lock_shared() { - std::unique_lock lk(mut_); - while ((state_ & write_entered_) || (state_ & n_readers_) == n_readers_) - gate1_.wait(lk); - count_t num_readers = (state_ & n_readers_) + 1; - state_ &= ~n_readers_; - state_ |= num_readers; - } - bool try_lock_shared() { - std::unique_lock lk(mut_); - count_t num_readers = state_ & n_readers_; - if (!(state_ & write_entered_) && num_readers != n_readers_) { - ++num_readers; - state_ &= ~n_readers_; - state_ |= num_readers; - return true; - } - return false; - } - template - bool try_lock_shared_for(const std::chrono::duration& rel_time) { - return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time); - } - template - bool try_lock_shared_until( - const std::chrono::time_point& abs_time); - void unlock_shared() { - std::lock_guard _(mut_); - count_t num_readers = (state_ & n_readers_) - 1; - state_ &= ~n_readers_; - state_ |= num_readers; - if (state_ & write_entered_) { - if (num_readers == 0) gate2_.notify_one(); - } else { - if (num_readers == n_readers_ - 1) gate1_.notify_one(); - } - } -}; - -template -bool shared_mutex::try_lock_until( - const std::chrono::time_point& abs_time) { - std::unique_lock lk(mut_); - if (state_ & write_entered_) { - while (true) { - std::cv_status status = gate1_.wait_until(lk, abs_time); - if ((state_ & write_entered_) == 0) break; - if (status == std::cv_status::timeout) return false; - } - } - state_ |= write_entered_; - if (state_ & n_readers_) { - while (true) { - std::cv_status status = gate2_.wait_until(lk, abs_time); - if ((state_ & n_readers_) == 0) break; - if (status == std::cv_status::timeout) { - state_ &= ~write_entered_; - return false; - } - } - } - return true; -} - -template -bool shared_mutex::try_lock_shared_until( - const std::chrono::time_point& abs_time) { - std::unique_lock lk(mut_); - if ((state_ & write_entered_) || (state_ & n_readers_) == n_readers_) { - while (true) { - std::cv_status status = gate1_.wait_until(lk, abs_time); - if ((state_ & write_entered_) == 0 && (state_ & n_readers_) < n_readers_) - break; - if (status == std::cv_status::timeout) return false; - } - } - count_t num_readers = (state_ & n_readers_) + 1; - state_ &= ~n_readers_; - state_ |= num_readers; - return true; -} - -template -class shared_lock { -public: - typedef Mutex mutex_type; - -private: - mutex_type* m_; - bool owns_; - - struct __nat { - int _; - }; - -public: - shared_lock() : m_(nullptr), owns_(false) {} - - explicit shared_lock(mutex_type& m) : m_(&m), owns_(true) { - m_->lock_shared(); - } - - shared_lock(mutex_type& m, std::defer_lock_t) : m_(&m), owns_(false) {} - - shared_lock(mutex_type& m, std::try_to_lock_t) - : m_(&m), owns_(m.try_lock_shared()) {} - - shared_lock(mutex_type& m, std::adopt_lock_t) : m_(&m), owns_(true) {} - - template - shared_lock(mutex_type& m, - const std::chrono::time_point& abs_time) - : m_(&m), owns_(m.try_lock_shared_until(abs_time)) {} - template - shared_lock(mutex_type& m, const std::chrono::duration& rel_time) - : m_(&m), owns_(m.try_lock_shared_for(rel_time)) {} - - ~shared_lock() { - if (owns_) m_->unlock_shared(); - } - - shared_lock(shared_lock const&) = delete; - shared_lock& operator=(shared_lock const&) = delete; - - shared_lock(shared_lock&& sl) : m_(sl.m_), owns_(sl.owns_) { - sl.m_ = nullptr; - sl.owns_ = false; - } - - shared_lock& operator=(shared_lock&& sl) { - if (owns_) m_->unlock_shared(); - m_ = sl.m_; - owns_ = sl.owns_; - sl.m_ = nullptr; - sl.owns_ = false; - return *this; - } - - explicit shared_lock(std::unique_lock&& ul) - : m_(ul.mutex()), owns_(ul.owns_lock()) { - if (owns_) m_->unlock_and_lock_shared(); - ul.release(); - } - - void lock(); - bool try_lock(); - template - bool try_lock_for(const std::chrono::duration& rel_time) { - return try_lock_until(std::chrono::steady_clock::now() + rel_time); - } - template - bool try_lock_until(const std::chrono::time_point& abs_time); - void unlock(); - - void swap(shared_lock&& u) { - std::swap(m_, u.m_); - std::swap(owns_, u.owns_); - } - - mutex_type* release() { - mutex_type* r = m_; - m_ = nullptr; - owns_ = false; - return r; - } - bool owns_lock() const { return owns_; } - operator int __nat::*() const { return owns_ ? &__nat::_ : 0; } - mutex_type* mutex() const { return m_; } -}; - -template -void shared_lock::lock() { - if (m_ == nullptr) - throw std::system_error(std::error_code(EPERM, std::system_category()), - "shared_lock::lock: references null mutex"); - if (owns_) - throw std::system_error(std::error_code(EDEADLK, std::system_category()), - "shared_lock::lock: already locked"); - m_->lock_shared(); - owns_ = true; -} - -template -bool shared_lock::try_lock() { - if (m_ == nullptr) - throw std::system_error(std::error_code(EPERM, std::system_category()), - "shared_lock::try_lock: references null mutex"); - if (owns_) - throw std::system_error(std::error_code(EDEADLK, std::system_category()), - "shared_lock::try_lock: already locked"); - owns_ = m_->try_lock_shared(); - return owns_; -} - -template -template -bool shared_lock::try_lock_until( - const std::chrono::time_point& abs_time) { - if (m_ == nullptr) - throw std::system_error( - std::error_code(EPERM, std::system_category()), - "shared_lock::try_lock_until: references null mutex"); - if (owns_) - throw std::system_error(std::error_code(EDEADLK, std::system_category()), - "shared_lock::try_lock_until: already locked"); - owns_ = m_->try_lock_shared_until(abs_time); - return owns_; -} - -template -void shared_lock::unlock() { - if (!owns_) - throw std::system_error(std::error_code(EPERM, std::system_category()), - "shared_lock::unlock: not locked"); - m_->unlock_shared(); - owns_ = false; -} - -template -inline void swap(shared_lock& x, shared_lock& y) { - x.swap(y); -} - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/string_view.h b/fast_tokenizer/fast_tokenizer/utils/string_view.h deleted file mode 100644 index 35cacdefed9f..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/string_view.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -struct simple_string_view { - const char* ptr_; - size_t offset_; - size_t size_; - explicit simple_string_view(const char* ptr = nullptr) - : ptr_(ptr), offset_(0), size_(0) { - while (ptr_ && ptr_[size_] != '\0') { - size_++; - } - } - simple_string_view(const char* ptr, size_t size) : ptr_(ptr), size_(size) {} - - const char* data() const { - if (!ptr_) { - return ptr_ + offset_; - } - return ptr_; - } - size_t size() const { return size_; } - bool empty() const { return size_ == 0; } - - void remove_prefix(size_t n) { - assert(n <= size_); - ptr_ += n; - size_ -= n; - } -}; - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/trie.cc b/fast_tokenizer/fast_tokenizer/utils/trie.cc deleted file mode 100644 index b063e91ff085..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/trie.cc +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2022 TF.Text Authors. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include - -#include "glog/logging.h" -#include "fast_tokenizer/utils/trie.h" -#include "fast_tokenizer/utils/utf8.h" -#include "fast_tokenizer/utils/utils.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -void Trie::CreateTrie(const std::vector& keys, - const std::vector& values) { - trie_ = std::make_shared(); - trie_->build(keys.size(), - const_cast(&keys[0]), - nullptr, - const_cast(&values[0])); - const uint32_t* trie_ptr = reinterpret_cast(trie_->array()); - trie_array_ = std::vector(trie_ptr, trie_ptr + trie_->size()); -} - -int Trie::EncodeTokenId(const std::string& token, uint32_t id) const { - bool is_suffix_token = (token.rfind(continuing_subword_prefix_) == 0); - uint32_t token_length = token.length(); - if (is_suffix_token) { - token_length -= continuing_subword_prefix_.length(); - } - return EncodeToken(id, token_length, is_suffix_token); -} - -void Trie::InitTrieSuffixRoot() { - auto node = CreateRootTraversalCursor(); - if (!TryTraverseSeveralSteps(&node, continuing_subword_prefix_)) { - throw std::runtime_error( - "Cannot locate suffix_root_. This should never happen."); - } - suffix_root_ = node.node_id_; -} - -void Trie::InitTrie(const std::vector& keys, - const std::vector& values) { - std::vector sorted_keys; - std::vector sorted_values; - GetSortedVocab(keys, values, &sorted_keys, &sorted_values); - CreateTrie(sorted_keys, sorted_values); - InitTrieSuffixRoot(); - if (with_pretokenization_ && keys.size() > 0) { - auto node = CreateRootTraversalCursor(); - if (!TryTraverseSeveralSteps(&node, - std::string(1, utils::kInvalidControlChar))) { - throw std::runtime_error( - "Cannot locate the dummy node for the failure link for punctuation " - "nodes. This should never happen."); - } - punct_failure_link_node_ = node.node_id_; - DeleteLinkFromParent(punct_failure_link_node_); - DeleteValueOfNode(punct_failure_link_node_); - } -} - -void Trie::AddPuncVocab( - std::vector* punc_vocab, - const std::unordered_map& vocab) const { - if (with_pretokenization_) { - for (uint32_t cp = 1; cp <= 0x0010FFFF; ++cp) { - if (!utils::IsUnicodeChar(cp) || !utils::IsPunctuationOrChineseChar(cp)) { - continue; - } - char utf_str[5]; - utils::GetUTF8Str(reinterpret_cast(&cp), utf_str, 1); - std::string punc_str(utf_str); - if (vocab.count(punc_str) == 0) { - punc_vocab->push_back(punc_str); - } - } - punc_vocab->push_back(std::string(1, utils::kInvalidControlChar)); - } -} - -void Trie::SetVocab(const std::unordered_map& vocab) { - std::vector keys; - std::vector values; - for (auto&& item : vocab) { - keys.push_back(item.first.c_str()); - values.push_back(EncodeTokenId(item.first, item.second)); - } - InitTrie(keys, values); -} - -void Trie::SetVocabList(const std::vector& keys) { - std::unordered_map vocab; - for (int i = 0; i < keys.size(); ++i) { - vocab[keys[i]] = i; - } - SetVocab(vocab); -} - -Trie::Trie(const std::string& continuing_subword_prefix, - const std::string& unk_token, - bool with_pretokenization) - : trie_(nullptr), - continuing_subword_prefix_(continuing_subword_prefix), - suffix_root_(utils::kNullNode), - punct_failure_link_node_(utils::kNullNode), - unk_token_(unk_token), - with_pretokenization_(with_pretokenization) {} - -Trie::Trie(const std::unordered_map& vocab, - const std::string& continuing_subword_prefix, - const std::string& unk_token, - bool with_pretokenization) - : continuing_subword_prefix_(continuing_subword_prefix), - unk_token_(unk_token), - suffix_root_(utils::kNullNode), - punct_failure_link_node_(utils::kNullNode), - with_pretokenization_(with_pretokenization) { - SetVocab(vocab); -} - -Trie::Trie(const std::vector& keys, - const std::string& continuing_subword_prefix, - const std::string& unk_token, - bool with_pretokenization) - : continuing_subword_prefix_(continuing_subword_prefix), - unk_token_(unk_token), - suffix_root_(utils::kNullNode), - punct_failure_link_node_(utils::kNullNode), - with_pretokenization_(with_pretokenization) { - SetVocabList(keys); -} - -Trie::TraversalCursor Trie::CreateRootTraversalCursor() const { - return CreateTraversalCursor(kRootNodeId); -} - -Trie::TraversalCursor Trie::CreateTraversalCursor(uint32_t node_id) const { - return Trie::TraversalCursor(node_id, trie_array_[node_id]); -} - -void Trie::SetTraversalCursor(Trie::TraversalCursor* cursor, - uint32_t node_id) const { - cursor->node_id_ = node_id; - cursor->unit_ = trie_array_[node_id]; -} - -bool Trie::TryTraverseOneStep(Trie::TraversalCursor* cursor, - unsigned char ch) const { - const uint32_t next_node_id = cursor->node_id_ ^ Offset(cursor->unit_) ^ ch; - const uint32_t next_node_unit = trie_array_[next_node_id]; - if (Label(next_node_unit) != ch) { - return false; - } - cursor->node_id_ = next_node_id; - cursor->unit_ = next_node_unit; - return true; -} - -bool Trie::TryTraverseSeveralSteps(Trie::TraversalCursor* cursor, - const std::string& path) const { - return TryTraverseSeveralSteps(cursor, path.data(), path.size()); -} - -bool Trie::TryTraverseSeveralSteps(Trie::TraversalCursor* cursor, - const char* ptr, - int size) const { - uint32_t cur_id = cursor->node_id_; - uint32_t cur_unit = cursor->unit_; - for (; size > 0; --size, ++ptr) { - const unsigned char ch = static_cast(*ptr); - cur_id ^= Offset(cur_unit) ^ ch; - cur_unit = trie_array_[cur_id]; - if (Label(cur_unit) != ch) { - return false; - } - } - cursor->node_id_ = cur_id; - cursor->unit_ = cur_unit; - return true; -} - -bool Trie::TryGetData(const Trie::TraversalCursor& cursor, - int* out_data) const { - if (!HasLeaf(cursor.unit_)) { - return false; - } - const uint32_t value_unit = - trie_array_[cursor.node_id_ ^ Offset(cursor.unit_)]; - *out_data = Value(value_unit); - return true; -} - -void Trie::DeleteValueOfNode(uint32_t node_id) { - trie_array_[node_id] &= 0xFFFFFEFF; -} - -void Trie::DeleteLinkFromParent(uint32_t child_node_id) { - trie_array_[child_node_id] &= 0xFFFFFF00; -} - -void Trie::SetWithPretokenization(bool with_pretokenization) { - with_pretokenization_ = with_pretokenization; -} - -void Trie::SetUNKToken(const std::string& unk_token) { unk_token_ = unk_token; } - -void Trie::SetContinuingSubwordPrefix( - const std::string& continuing_subword_prefix) { - continuing_subword_prefix_ = continuing_subword_prefix; -} - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/trie.h b/fast_tokenizer/fast_tokenizer/utils/trie.h deleted file mode 100644 index b4c9b0cbff4d..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/trie.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2022 TF.Text Authors. -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 - -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once -#include -#include -#include -#include -#include -#include -#include "darts.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -class Trie { -public: - static constexpr uint32_t kRootNodeId = 0; - - Trie(const std::string& continuing_subword_prefix = "##", - const std::string& unk_token = "[UNK]", - bool with_pretokenization = false); - Trie(const std::unordered_map& vocab, - const std::string& continuing_subword_prefix = "##", - const std::string& unk_token = "[UNK]", - bool with_pretokenization = false); - Trie(const std::vector& keys, - const std::string& continuing_subword_prefix = "##", - const std::string& unk_token = "[UNK]", - bool with_pretokenization = false); - struct TraversalCursor { - uint32_t node_id_; - uint32_t unit_; - TraversalCursor(uint32_t node_id = 0, uint32_t unit = 0) - : node_id_(node_id), unit_(unit) {} - }; - - TraversalCursor CreateRootTraversalCursor() const; - TraversalCursor CreateTraversalCursor(uint32_t node_id) const; - void SetTraversalCursor(TraversalCursor* cursor, uint32_t node_id) const; - bool TryTraverseOneStep(TraversalCursor* cursor, unsigned char ch) const; - bool TryTraverseSeveralSteps(TraversalCursor* cursor, - const std::string& path) const; - bool TryGetData(const TraversalCursor& cursor, int* out_data) const; - void SetVocab(const std::unordered_map& vocab); - void SetVocabList(const std::vector& vocab); - void SetWithPretokenization(bool with_pretokenization_); - void SetUNKToken(const std::string& unk_token); - void SetContinuingSubwordPrefix(const std::string& continuing_subword_prefix); - - uint32_t Size() const { - if (trie_.get() != nullptr) { - return trie_->size(); - } - return 0; - } - std::string GetContinuingSubwordPrefix() const { - return continuing_subword_prefix_; - } - uint32_t GetSuffixRoot() const { return suffix_root_; } - uint32_t GetPuncFailureNode() const { return punct_failure_link_node_; } - void DeleteValueOfNode(uint32_t node_id); - void DeleteLinkFromParent(uint32_t child_node_id); - -private: - void AddPuncVocab( - std::vector* punc_vocab, - const std::unordered_map& vocab) const; - void InitTrieSuffixRoot(); - void InitTrie(const std::vector& keys, - const std::vector& values); - int EncodeTokenId(const std::string& token, uint32_t id) const; - void CreateTrie(const std::vector& keys, - const std::vector& values); - - bool TryTraverseSeveralSteps(TraversalCursor* cursor, - const char* ptr, - int size) const; - - static uint32_t Offset(uint32_t unit) { - return (unit >> 10) << ((unit & 0x200) >> 6); - } - - // Returns a label associated with a node. - // A leaf node will have the MSB set and thus return an invalid label. - static uint32_t Label(uint32_t unit) { return unit & 0x800000ff; } - - // Returns whether a node has a leaf as a child. - static bool HasLeaf(uint32_t unit) { return unit & 0x100; } - - // Returns a value associated with a node. Available when a node is a leaf. - static int Value(uint32_t unit) { - return static_cast(unit & 0x7fffffff); - } - - std::shared_ptr trie_; - std::vector trie_array_; - std::string continuing_subword_prefix_; - std::string unk_token_; - uint32_t suffix_root_; - uint32_t punct_failure_link_node_; - bool with_pretokenization_; -}; - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/unique_ptr.h b/fast_tokenizer/fast_tokenizer/utils/unique_ptr.h deleted file mode 100644 index 767e203fcd2d..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/unique_ptr.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -// Trait to select overloads and return types for MakeUnique. -template -struct MakeUniqueResult { - using scalar = std::unique_ptr; -}; -template -struct MakeUniqueResult { - using array = std::unique_ptr; -}; -template -struct MakeUniqueResult { - using invalid = void; -}; - -// MakeUnique(...) is an early implementation of C++14 std::make_unique. -// It is designed to be 100% compatible with std::make_unique so that the -// eventual switchover will be a simple renaming operation. -template -typename MakeUniqueResult::scalar make_unique(Args &&... args) { // NOLINT - return std::unique_ptr( - new T(std::forward(args)...)); // NOLINT(build/c++11) -} - -// Overload for array of unknown bound. -// The allocation of arrays needs to use the array form of new, -// and cannot take element constructor arguments. -template -typename MakeUniqueResult::array make_unique(size_t n) { - return std::unique_ptr(new typename std::remove_extent::type[n]()); -} - -// Reject arrays of known bound. -template -typename MakeUniqueResult::invalid make_unique(Args &&... /* args */) = - delete; // NOLINT - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/utf8.h b/fast_tokenizer/fast_tokenizer/utils/utf8.h deleted file mode 100644 index dbb8c92f6732..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/utf8.h +++ /dev/null @@ -1,225 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once -#include - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -static constexpr uint32_t kUnicodeError = 0xFFFD; - -inline bool IsUnicodeNonChar(uint32_t c) { - return ((c) >= 0xfdd0 && ((c) <= 0xfdef || ((c)&0xfffe) == 0xfffe) && - (c) <= 0x10ffff); -} - -inline bool IsUnicodeChar(uint32_t c) { - return ((c) < 0xd800 || - (0xdfff < (c) && (c) <= 0x10ffff && !IsUnicodeNonChar(c))); -} - -inline uint32_t BytesInUTF8Char(uint8_t byte) { - unsigned int count = 1; - // no if-statements means no divergence - count += static_cast((byte & 0xF0) == 0xF0); - count += static_cast((byte & 0xE0) == 0xE0); - count += static_cast((byte & 0xC0) == 0xC0); - count -= static_cast((byte & 0xC0) == 0x80); - return count; -} - -inline uint32_t GetUnicodeLenFromUTF8(const char* pSrc, size_t length) { - size_t unicode_len = 0; - size_t start = 0; - while (start < length && pSrc[start] != '\0') { - size_t chwidth = BytesInUTF8Char(pSrc[start]); - start += chwidth; - ++unicode_len; - } - return unicode_len; -} - -inline uint32_t UTF8ToUInt32(const char* pSrc, uint32_t* chr) { - uint32_t chwidth = BytesInUTF8Char(static_cast(*pSrc)); - *chr = static_cast(*pSrc++) & 0xFF; - if (chwidth > 1) { - *chr = (*chr) << 8; - *chr |= (static_cast(*pSrc++) & 0xFF); // << 8; - if (chwidth > 2) { - *chr = (*chr) << 8; - *chr |= (static_cast(*pSrc++) & 0xFF); // << 16; - if (chwidth > 3) { - *chr = (*chr) << 8; - *chr |= (static_cast(*pSrc++) & 0xFF); // << 24; - } - } - } - return chwidth; -} - -inline uint32_t UTF8ToUnicode(uint32_t utf8) { - uint32_t unchr = 0; - if (utf8 < 0x00000080) { - unchr = utf8; - } else if (utf8 < 0x0000E000) { - unchr = (utf8 & 0x1F00) >> 2; - unchr |= (utf8 & 0x003F); - } else if (utf8 < 0x00F00000) { - unchr = (utf8 & 0x0F0000) >> 4; - unchr |= (utf8 & 0x003F00) >> 2; - unchr |= (utf8 & 0x00003F); - } else if (utf8 <= static_cast(0xF8000000)) { - unchr = (utf8 & 0x03000000) >> 6; - unchr |= (utf8 & 0x003F0000) >> 4; - unchr |= (utf8 & 0x00003F00) >> 2; - unchr |= (utf8 & 0x0000003F); - } - return unchr; -} - -inline bool IsCharBeginBoundary(char ch) { - return ((~ch) >> 7) || ((ch & 0xC0) == 0xC0); -} - -inline bool IsCharBoundary(const char* ch) { - return IsCharBeginBoundary(*ch) || IsCharBeginBoundary(*(ch + 1)); -} - -inline uint32_t UnicodeToUTF8(uint32_t unchr) { - uint32_t utf8 = 0; - if (unchr < 0x00000080) { - utf8 = unchr; - } else if (unchr < 0x00000800) { - utf8 = (unchr << 2) & 0x1F00; - utf8 |= (unchr & 0x3F); - utf8 |= 0x0000C080; - } else if (unchr < 0x00010000) { - utf8 = (unchr << 4) & 0x0F0000; // upper 4 bits - utf8 |= (unchr << 2) & 0x003F00; // next 6 bits - utf8 |= (unchr & 0x3F); // last 6 bits - utf8 |= 0x00E08080; - } else if (unchr < 0x00110000) { // 3-byte unicode - utf8 = (unchr << 6) & 0x07000000; // upper 3 bits - utf8 |= (unchr << 4) & 0x003F0000; // next 6 bits - utf8 |= (unchr << 2) & 0x00003F00; // next 6 bits - utf8 |= (unchr & 0x3F); // last 6 bits - utf8 |= static_cast(0xF0808080); - } - return utf8; -} - -inline uint32_t BytesInUnicodeChar(uint32_t chr) { - uint32_t count = 1; - // no if-statements means no divergence - count += static_cast((chr & static_cast(0x0000FF00)) > 0); - count += static_cast((chr & static_cast(0x00FF0000)) > 0); - count += static_cast((chr & static_cast(0xFF000000)) > 0); - return count; -} - -inline uint32_t UnicodeToUTF8Char(uint32_t chr, char* dst) { - uint32_t chwidth = BytesInUnicodeChar(chr); - for (uint32_t idx = 0; idx < chwidth; ++idx) { - dst[chwidth - idx - 1] = static_cast(chr & 0xFF); - chr = chr >> 8; - } - return chwidth; -} - -inline uint32_t GetUTF8CharLen(uint32_t u32chr) { - return BytesInUnicodeChar(UnicodeToUTF8(u32chr)); -} - -inline void GetUTF8Str(const char32_t* unicode_str, - char* utf8_str, - size_t unicode_len) { - char dst_char[5] = {0}; - for (size_t i = 0; i < unicode_len; ++i) { - uint32_t utf8_uint32 = UnicodeToUTF8(unicode_str[i]); - uint32_t utf8_char_count = UnicodeToUTF8Char(utf8_uint32, dst_char); - dst_char[utf8_char_count] = '\0'; - memcpy(utf8_str, dst_char, utf8_char_count); - utf8_str += utf8_char_count; - } - *utf8_str = '\0'; -} - -inline void GetUnicodeStr(const char* pSrc, - char32_t* unicode_str, - size_t unicode_len) { - uint32_t curr_unicode_char; - uint32_t count = UTF8ToUInt32(pSrc, &curr_unicode_char); - curr_unicode_char = UTF8ToUnicode(curr_unicode_char); - for (size_t i = 0; i < unicode_len; ++i) { - unicode_str[i] = curr_unicode_char; - pSrc += count; - count = UTF8ToUInt32(pSrc, &curr_unicode_char); - curr_unicode_char = UTF8ToUnicode(curr_unicode_char); - } -} - -inline bool IsTrailByte(char x) { return static_cast(x) < -0x40; } - -inline bool IsValidCodepoint(char32_t c) { - return (static_cast(c) < 0xD800) || (c >= 0xE000 && c <= 0x10FFFF); -} - -// mblen sotres the number of bytes consumed after decoding. -inline uint32_t DecodeUTF8(const char* begin, const char* end, size_t* mblen) { - const size_t len = end - begin; - - if (static_cast(begin[0]) < 0x80) { - *mblen = 1; - return static_cast(begin[0]); - } else if (len >= 2 && (begin[0] & 0xE0) == 0xC0) { - const uint32_t cp = (((begin[0] & 0x1F) << 6) | ((begin[1] & 0x3F))); - if (IsTrailByte(begin[1]) && cp >= 0x0080 && IsValidCodepoint(cp)) { - *mblen = 2; - return cp; - } - } else if (len >= 3 && (begin[0] & 0xF0) == 0xE0) { - const uint32_t cp = (((begin[0] & 0x0F) << 12) | ((begin[1] & 0x3F) << 6) | - ((begin[2] & 0x3F))); - if (IsTrailByte(begin[1]) && IsTrailByte(begin[2]) && cp >= 0x0800 && - IsValidCodepoint(cp)) { - *mblen = 3; - return cp; - } - } else if (len >= 4 && (begin[0] & 0xf8) == 0xF0) { - const uint32_t cp = (((begin[0] & 0x07) << 18) | ((begin[1] & 0x3F) << 12) | - ((begin[2] & 0x3F) << 6) | ((begin[3] & 0x3F))); - if (IsTrailByte(begin[1]) && IsTrailByte(begin[2]) && - IsTrailByte(begin[3]) && cp >= 0x10000 && IsValidCodepoint(cp)) { - *mblen = 4; - return cp; - } - } - - // Invalid UTF-8. - *mblen = 1; - return kUnicodeError; -} - -inline bool IsValidDecodeUTF8(const char* begin, - const char* end, - size_t* mblen) { - const uint32_t c = DecodeUTF8(begin, end, mblen); - return c != kUnicodeError || *mblen == 3; -} - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/utils.cc b/fast_tokenizer/fast_tokenizer/utils/utils.cc deleted file mode 100644 index dd23726c1bf4..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/utils.cc +++ /dev/null @@ -1,147 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#include "fast_tokenizer/utils/utils.h" - -#include "unicode/uchar.h" - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -void GetVocabFromFiles(const std::string& files, - std::unordered_map* vocab) { - const static std::string WHITESPACE = " \n\r\t\f\v"; - std::ifstream fin(files); - if (!fin.good()) { - std::cerr << "The vocab file " << files - << " seems to be unable to access" - " or non-exists, please check again. " - << std::endl; - return; - } - vocab->clear(); - int i = 0; - constexpr int MAX_BUFFER_SIZE = 256; - char word[MAX_BUFFER_SIZE]; - while (fin.getline(word, MAX_BUFFER_SIZE)) { - std::string word_str = word; - auto leading_spaces = word_str.find_first_not_of(WHITESPACE); - if (leading_spaces != std::string::npos) { - leading_spaces = (std::min)(leading_spaces, word_str.length() - 1); - word_str = word_str.substr(leading_spaces); - } - auto trailing_spaces = word_str.find_last_not_of(WHITESPACE); - if (trailing_spaces != std::string::npos) { - word_str = word_str.substr(0, trailing_spaces + 1); - } - if (word_str != "") { - (*vocab)[word_str] = i++; - } - } -} - -bool IsChineseChar(int ch) { - return ( - (ch >= 0x4E00 && ch <= 0x9FFF) || (ch >= 0x3400 && ch <= 0x4DBF) || - (ch >= 0x20000 && ch <= 0x2A6DF) || (ch >= 0x2A700 && ch <= 0x2B73F) || - (ch >= 0x2B740 && ch <= 0x2B81F) || (ch >= 0x2B820 && ch <= 0x2CEAF) || - (ch >= 0xF900 && ch <= 0xFAFF) || (ch >= 0x2F800 && ch <= 0x2FA1F)); -} - -bool IsPunctuation(int ch) { - return (ch >= 33 && ch <= 47) || (ch >= 58 && ch <= 64) || - (ch >= 91 && ch <= 96) || (ch >= 123 && ch <= 126) || u_ispunct(ch); -} - -bool IsPunctuationOrChineseChar(int ch) { - return IsChineseChar(ch) || IsPunctuation(ch); -} - -bool StringReplace(std::string* str, - const std::string& from, - const std::string& to) { - size_t start_pos = str->find(from); - if (start_pos == std::string::npos) return false; - str->replace(start_pos, from.length(), to); - return true; -} - -void StringReplaceAll(std::string* str, - const std::string& from, - const std::string& to) { - if (from.empty()) return; - size_t start_pos = 0; - while ((start_pos = str->find(from, start_pos)) != std::string::npos) { - str->replace(start_pos, from.length(), to); - start_pos += to.length(); // In case 'to' contains 'from', like replacing - // 'x' with 'yx' - } -} - -void GetSortedVocab(const std::vector& keys, - const std::vector& values, - std::vector* sorted_keys, - std::vector* sorted_values) { - // Sort the vocab - std::vector sorted_vocab_index(keys.size()); - std::iota(sorted_vocab_index.begin(), sorted_vocab_index.end(), 0); - std::sort(sorted_vocab_index.begin(), - sorted_vocab_index.end(), - [&keys](const int a, const int b) { - return std::strcmp(keys[a], keys[b]) < 0; - }); - - sorted_keys->resize(keys.size()); - sorted_values->resize(keys.size()); - for (int i = 0; i < sorted_vocab_index.size(); ++i) { - auto idx = sorted_vocab_index[i]; - (*sorted_keys)[i] = keys[idx]; - (*sorted_values)[i] = values[idx]; - } -} - -std::unordered_map CreateBytesToChars() { - std::unordered_map bytes_to_chars; - bool bytes_flag[256] = {false}; - std::vector> ranges = { - {'!', '~'}, {'\xA1', '\xAC'}, {'\xAE', '\xFF'}}; - - for (int i = 0; i < ranges.size(); ++i) { - for (uint32_t c = ranges[i].first; c <= ranges[i].second; ++c) { - bytes_to_chars.insert({c, c}); - bytes_flag[c] = true; - } - } - uint32_t n = 0; - for (uint32_t b = 0; b <= 255; ++b) { - if (!bytes_flag[b]) { - bytes_to_chars.insert({b, (1 << 8) + n}); - n += 1; - } - } - return bytes_to_chars; -} - -bool IsWhiteSpace(int ch) { - const std::string WHITESPACE = " \n\r\t\f\v"; - for (int i = 0; i < WHITESPACE.length(); ++i) { - if (ch == WHITESPACE[i]) return true; - } - return u_isspace(ch); -} - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/utils.h b/fast_tokenizer/fast_tokenizer/utils/utils.h deleted file mode 100644 index 6a9e418693d3..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/utils.h +++ /dev/null @@ -1,187 +0,0 @@ -/* Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_FREEBSD) -#include -#endif -#if !defined(__APPLE__) && !defined(_WIN32) && !defined(_FREEBSD) -#include -#if BYTE_ORDER == __BIG_ENDIAN -#define IS_BIG_ENDIAN -#endif -#endif - -#if defined(_WIN32) -#ifdef FASTTOKENIZER_LIB -#define FASTTOKENIZER_DECL __declspec(dllexport) -#else -#define FASTTOKENIZER_DECL __declspec(dllimport) -#endif // FASTTOKENIZER_LIB -#else -#define FASTTOKENIZER_DECL __attribute__((visibility("default"))) -#endif // _WIN32 - -namespace paddlenlp { -namespace fast_tokenizer { -namespace utils { - -void GetVocabFromFiles(const std::string& files, - std::unordered_map* vocab); - -bool IsChineseChar(int ch); - -bool IsPunctuation(int ch); - -bool IsPunctuationOrChineseChar(int ch); - -bool StringReplace(std::string* str, - const std::string& from, - const std::string& to); - -void StringReplaceAll(std::string* str, - const std::string& from, - const std::string& to); - -// Used in fast wordpiece model -static constexpr uint32_t kBitToIndicateSuffixToken = 30; - -static constexpr uint32_t kBitsToEncodeVocabTokenLength = 8; - -static constexpr uint32_t kMaskToEncodeVocabTokenLength = - (1 << kBitsToEncodeVocabTokenLength) - 1; - -static constexpr uint32_t kMaxVocabTokenLengthInUTF8Bytes = - (1 << kBitsToEncodeVocabTokenLength); - -static constexpr uint32_t kMaxSupportedVocabSize = - (1 << (32 - 1 - 1 - kBitsToEncodeVocabTokenLength)); - -static constexpr uint32_t kMaskToEncodeVocabTokenId = - ((1 << kBitToIndicateSuffixToken) - 1) ^ kMaskToEncodeVocabTokenLength; - -inline int EncodeToken(uint32_t token_id, - uint32_t token_length, - bool is_suffix_token) { - int encoded_value = (is_suffix_token << kBitToIndicateSuffixToken) | - (token_id << kBitsToEncodeVocabTokenLength) | - (token_length - 1); - return encoded_value; -} - -inline bool IsSuffixTokenFromEncodedValue(int token_encoded_value) { - return static_cast(token_encoded_value >> kBitToIndicateSuffixToken); -} - -// Gets the token id from the encoded value. -inline int GetTokenIdFromEncodedValue(int token_encoded_value) { - return (token_encoded_value & kMaskToEncodeVocabTokenId) >> - kBitsToEncodeVocabTokenLength; -} - -// Gets the token length (without the suffix indicator) from the encoded value. -inline int GetTokenLengthFromEncodedValue(int token_encoded_value) { - return (token_encoded_value & kMaskToEncodeVocabTokenLength) + 1; -} - -static constexpr uint32_t kBitsToEncodeFailurePopsListSize = - kBitsToEncodeVocabTokenLength; - -static constexpr uint32_t kMaskToEncodeFailurePopsListSize = - (1 << kBitsToEncodeFailurePopsListSize) - 1; - -static constexpr uint32_t kMaxFailurePopsListSize = - (1 << kBitsToEncodeFailurePopsListSize); - -static constexpr uint32_t kMaxSupportedFailurePoolOffset = - (1 << (32 - kBitsToEncodeFailurePopsListSize)) - 1 - 1; - -static constexpr uint32_t kNullFailurePopsList = - std::numeric_limits::max(); - -inline uint32_t EncodeFailurePopList(int offset, int length) { - return (offset << kBitsToEncodeFailurePopsListSize) | (length - 1); -} - -inline void GetFailurePopsOffsetAndLength(uint32_t offset_and_length, - int* out_offset, - int* out_length) { - *out_offset = offset_and_length >> kBitsToEncodeFailurePopsListSize; - *out_length = (offset_and_length & kMaskToEncodeFailurePopsListSize) + 1; -} - -static constexpr uint32_t kNullNode = std::numeric_limits::max(); - -static constexpr uint32_t kMaxSupportedTrieSize = - std::numeric_limits::max(); - -// A Unicode control char that never appears in the input as it is filtered -// during text normalization. It is used to build dummy nodes in the trie. -static constexpr char kInvalidControlChar = 0x11; - -inline bool IsSuffixWord(const std::string& word, - const std::string& continuing_subword_prefix) { - return word.rfind(continuing_subword_prefix) == 0; -} - -template -inline bool DecodePOD(const char* str, size_t str_len, T* result) { - if (sizeof(*result) != str_len) { - return false; - } - memcpy(result, str, sizeof(T)); - return true; -} - - -template -inline std::string EncodePOD(const T& value) { - std::string s; - s.resize(sizeof(T)); - memcpy(const_cast(s.data()), &value, sizeof(T)); - return s; -} - -inline size_t OneCharLen(const char* src) { - return "\1\1\1\1\1\1\1\1\1\1\1\1\2\2\3\4"[(*src & 0xFF) >> 4]; -} - -#ifdef IS_BIG_ENDIAN -inline uint32 Swap32(uint32 x) { return __builtin_bswap32(x); } -#endif - -void GetSortedVocab(const std::vector& keys, - const std::vector& values, - std::vector* sorted_keys, - std::vector* sorted_values); - -std::unordered_map CreateBytesToChars(); - -bool IsWhiteSpace(int ch); - -} // namespace utils -} // namespace fast_tokenizer -} // namespace paddlenlp diff --git a/fast_tokenizer/fast_tokenizer/utils/variant.h b/fast_tokenizer/fast_tokenizer/utils/variant.h deleted file mode 100644 index 3429c2ce15b3..000000000000 --- a/fast_tokenizer/fast_tokenizer/utils/variant.h +++ /dev/null @@ -1,2859 +0,0 @@ -// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Copy from -// https://github.com/mpark/variant/blob/single-header/v1.4.0/variant.hpp -// Modify the following points: -// 1. modify namespace mpark to namespace paddlenlp -// 2. add type() member function for variant class -// 3. remove the visitation implementation under the branhch with -// MPARK_CPP14_CONSTEXPR defined since lib::cpp14::array could not be converted -// to std::initializer_list in Paddle's compilation -// 4. decorate PYBIND11_HIDDEN for struct value_visitor - -// MPark.Variant -// -// Copyright Michael Park, 2015-2017 -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.md or copy at -// http://boost.org/LICENSE_1_0.txt) - -#pragma once - -// gcc >= 9 has a bug that creates a false positive warning. -// Reference: -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92145 -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89381 -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 9 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-copy" -#endif - -/* - variant synopsis - -namespace std { - - // 20.7.2, class template variant - template - class variant { - public: - - // 20.7.2.1, constructors - constexpr variant() noexcept(see below); - variant(const variant&); - variant(variant&&) noexcept(see below); - - template constexpr variant(T&&) noexcept(see below); - - template - constexpr explicit variant(in_place_type_t, Args&&...); - - template - constexpr explicit variant( - in_place_type_t, initializer_list, Args&&...); - - template - constexpr explicit variant(in_place_index_t, Args&&...); - - template - constexpr explicit variant( - in_place_index_t, initializer_list, Args&&...); - - // 20.7.2.2, destructor - ~variant(); - - // 20.7.2.3, assignment - variant& operator=(const variant&); - variant& operator=(variant&&) noexcept(see below); - - template variant& operator=(T&&) noexcept(see below); - - // 20.7.2.4, modifiers - template - T& emplace(Args&&...); - - template - T& emplace(initializer_list, Args&&...); - - template - variant_alternative& emplace(Args&&...); - - template - variant_alternative& emplace(initializer_list, Args&&...); - - // 20.7.2.5, value status - constexpr bool valueless_by_exception() const noexcept; - constexpr size_t index() const noexcept; - - // 20.7.2.6, swap - void swap(variant&) noexcept(see below); - }; - - // 20.7.3, variant helper classes - template struct variant_size; // undefined - - template - constexpr size_t variant_size_v = variant_size::value; - - template struct variant_size; - template struct variant_size; - template struct variant_size; - - template - struct variant_size>; - - template struct variant_alternative; // undefined - - template - using variant_alternative_t = typename variant_alternative::type; - - template struct variant_alternative; - template struct variant_alternative; - template struct variant_alternative; - - template - struct variant_alternative>; - - constexpr size_t variant_npos = -1; - - // 20.7.4, value access - template - constexpr bool holds_alternative(const variant&) noexcept; - - template - constexpr variant_alternative_t>& - get(variant&); - - template - constexpr variant_alternative_t>&& - get(variant&&); - - template - constexpr variant_alternative_t> const& - get(const variant&); - - template - constexpr variant_alternative_t> const&& - get(const variant&&); - - template - constexpr T& get(variant&); - - template - constexpr T&& get(variant&&); - - template - constexpr const T& get(const variant&); - - template - constexpr const T&& get(const variant&&); - - template - constexpr add_pointer_t>> - get_if(variant*) noexcept; - - template - constexpr add_pointer_t>> - get_if(const variant*) noexcept; - - template - constexpr add_pointer_t - get_if(variant*) noexcept; - - template - constexpr add_pointer_t - get_if(const variant*) noexcept; - - // 20.7.5, relational operators - template - constexpr bool operator==(const variant&, const variant&); - - template - constexpr bool operator!=(const variant&, const variant&); - - template - constexpr bool operator<(const variant&, const variant&); - - template - constexpr bool operator>(const variant&, const variant&); - - template - constexpr bool operator<=(const variant&, const variant&); - - template - constexpr bool operator>=(const variant&, const variant&); - - // 20.7.6, visitation - template - constexpr see below visit(Visitor&&, Variants&&...); - - // 20.7.7, class monostate - struct monostate; - - // 20.7.8, monostate relational operators - constexpr bool operator<(monostate, monostate) noexcept; - constexpr bool operator>(monostate, monostate) noexcept; - constexpr bool operator<=(monostate, monostate) noexcept; - constexpr bool operator>=(monostate, monostate) noexcept; - constexpr bool operator==(monostate, monostate) noexcept; - constexpr bool operator!=(monostate, monostate) noexcept; - - // 20.7.9, specialized algorithms - template - void swap(variant&, variant&) noexcept(see below); - - // 20.7.10, class bad_variant_access - class bad_variant_access; - - // 20.7.11, hash support - template struct hash; - template struct hash>; - template <> struct hash; - -} // namespace std - -*/ - -#include -#include -#include -#include -#include -#include -#include - -// MPark.Variant -// -// Copyright Michael Park, 2015-2017 -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.md or copy at -// http://boost.org/LICENSE_1_0.txt) - -#ifndef MPARK_CONFIG_HPP -#define MPARK_CONFIG_HPP - -// MSVC 2015 Update 3. -#if __cplusplus < 201103L && (!defined(_MSC_VER) || _MSC_FULL_VER < 190024210) -#error "MPark.Variant requires C++11 support." -#endif - -#ifndef __has_attribute -#define __has_attribute(x) 0 -#endif - -#ifndef __has_builtin -#define __has_builtin(x) 0 -#endif - -#ifndef __has_include -#define __has_include(x) 0 -#endif - -#ifndef __has_feature -#define __has_feature(x) 0 -#endif - -#if __has_attribute(always_inline) || defined(__GNUC__) -#define MPARK_ALWAYS_INLINE __attribute__((__always_inline__)) inline -#elif defined(_MSC_VER) -#define MPARK_ALWAYS_INLINE __forceinline -#else -#define MPARK_ALWAYS_INLINE inline -#endif - -#if __has_builtin(__builtin_addressof) || \ - (defined(__GNUC__) && __GNUC__ >= 7) || defined(_MSC_VER) -#define MPARK_BUILTIN_ADDRESSOF -#endif - -#if __has_builtin(__builtin_unreachable) || defined(__GNUC__) -#define MPARK_BUILTIN_UNREACHABLE __builtin_unreachable() -#elif defined(_MSC_VER) -#define MPARK_BUILTIN_UNREACHABLE __assume(false) -#else -#define MPARK_BUILTIN_UNREACHABLE -#endif - -#if __has_builtin(__type_pack_element) -#define MPARK_TYPE_PACK_ELEMENT -#endif - -#if defined(__cpp_constexpr) && __cpp_constexpr >= 200704 && \ - !(defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 9) -#define MPARK_CPP11_CONSTEXPR -#endif - -#if defined(__cpp_constexpr) && __cpp_constexpr >= 201304 -#define MPARK_CPP14_CONSTEXPR -#endif - -#if __has_feature(cxx_exceptions) || defined(__cpp_exceptions) || \ - (defined(_MSC_VER) && defined(_CPPUNWIND)) -#define MPARK_EXCEPTIONS -#endif - -#if defined(__cpp_generic_lambdas) || defined(_MSC_VER) -#define MPARK_GENERIC_LAMBDAS -#endif - -#if defined(__cpp_lib_integer_sequence) -#define MPARK_INTEGER_SEQUENCE -#endif - -#if defined(__cpp_return_type_deduction) || defined(_MSC_VER) -#define MPARK_RETURN_TYPE_DEDUCTION -#endif - -#if defined(__cpp_lib_transparent_operators) || defined(_MSC_VER) -#define MPARK_TRANSPARENT_OPERATORS -#endif - -#if defined(__cpp_variable_templates) || defined(_MSC_VER) -#define MPARK_VARIABLE_TEMPLATES -#endif - -#if !defined(__GLIBCXX__) || __has_include() // >= libstdc++-5 -#define MPARK_TRIVIALITY_TYPE_TRAITS -#define MPARK_INCOMPLETE_TYPE_TRAITS -#endif - -#endif // MPARK_CONFIG_HPP - -// MPark.Variant -// -// Copyright Michael Park, 2015-2017 -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.md or copy at -// http://boost.org/LICENSE_1_0.txt) - -#ifndef MPARK_IN_PLACE_HPP -#define MPARK_IN_PLACE_HPP - -#include - -namespace paddlenlp { - -struct in_place_t { - explicit in_place_t() = default; -}; - -template -struct in_place_index_t { - explicit in_place_index_t() = default; -}; - -template -struct in_place_type_t { - explicit in_place_type_t() = default; -}; - -#ifdef MPARK_VARIABLE_TEMPLATES -constexpr in_place_t in_place{}; - -template -constexpr in_place_index_t in_place_index{}; - -template -constexpr in_place_type_t in_place_type{}; -#endif - -} // namespace paddlenlp - -#endif // MPARK_IN_PLACE_HPP - -// MPark.Variant -// -// Copyright Michael Park, 2015-2017 -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.md or copy at -// http://boost.org/LICENSE_1_0.txt) - -#ifndef MPARK_LIB_HPP -#define MPARK_LIB_HPP - -#include -#include -#include -#include - -#define MPARK_RETURN(...) \ - noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { return __VA_ARGS__; } - -namespace paddlenlp { -namespace lib { -template -struct identity { - using type = T; -}; - -inline namespace cpp14 { -template -struct array { - constexpr const T &operator[](std::size_t index) const { return data[index]; } - - T data[N == 0 ? 1 : N]; -}; - -template -using add_pointer_t = typename std::add_pointer::type; - -template -using common_type_t = typename std::common_type::type; - -template -using decay_t = typename std::decay::type; - -template -using enable_if_t = typename std::enable_if::type; - -template -using remove_const_t = typename std::remove_const::type; - -template -using remove_reference_t = typename std::remove_reference::type; - -template -inline constexpr T &&forward(remove_reference_t &t) noexcept { - return static_cast(t); -} - -template -inline constexpr T &&forward(remove_reference_t &&t) noexcept { - static_assert(!std::is_lvalue_reference::value, - "can not forward an rvalue as an lvalue"); - return static_cast(t); -} - -template -inline constexpr remove_reference_t &&move(T &&t) noexcept { - return static_cast &&>(t); -} - -#ifdef MPARK_INTEGER_SEQUENCE -using std::index_sequence; -using std::index_sequence_for; -using std::integer_sequence; -using std::make_index_sequence; -#else -template -struct integer_sequence { - using value_type = T; - static constexpr std::size_t size() noexcept { return sizeof...(Is); } -}; - -template -using index_sequence = integer_sequence; - -template -struct make_index_sequence_concat; - -template -struct make_index_sequence_concat, - index_sequence> - : identity> {}; - -template -struct make_index_sequence_impl; - -template -using make_index_sequence = typename make_index_sequence_impl::type; - -template -struct make_index_sequence_impl - : make_index_sequence_concat, - make_index_sequence> {}; - -template <> -struct make_index_sequence_impl<0> : identity> {}; - -template <> -struct make_index_sequence_impl<1> : identity> {}; - -template -using index_sequence_for = make_index_sequence; -#endif - -// -#ifdef MPARK_TRANSPARENT_OPERATORS -using equal_to = std::equal_to<>; -#else -struct equal_to { - template - inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const - MPARK_RETURN(lib::forward(lhs) == lib::forward(rhs)) -}; -#endif - -#ifdef MPARK_TRANSPARENT_OPERATORS -using not_equal_to = std::not_equal_to<>; -#else -struct not_equal_to { - template - inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const - MPARK_RETURN(lib::forward(lhs) != lib::forward(rhs)) -}; -#endif - -#ifdef MPARK_TRANSPARENT_OPERATORS -using less = std::less<>; -#else -struct less { - template - inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const - MPARK_RETURN(lib::forward(lhs) < lib::forward(rhs)) -}; -#endif - -#ifdef MPARK_TRANSPARENT_OPERATORS -using greater = std::greater<>; -#else -struct greater { - template - inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const - MPARK_RETURN(lib::forward(lhs) > lib::forward(rhs)) -}; -#endif - -#ifdef MPARK_TRANSPARENT_OPERATORS -using less_equal = std::less_equal<>; -#else -struct less_equal { - template - inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const - MPARK_RETURN(lib::forward(lhs) <= lib::forward(rhs)) -}; -#endif - -#ifdef MPARK_TRANSPARENT_OPERATORS -using greater_equal = std::greater_equal<>; -#else -struct greater_equal { - template - inline constexpr auto operator()(Lhs &&lhs, Rhs &&rhs) const - MPARK_RETURN(lib::forward(lhs) >= lib::forward(rhs)) -}; -#endif -} // namespace cpp14 - -inline namespace cpp17 { -// -template -using bool_constant = std::integral_constant; - -template -struct voider : identity {}; - -template -using void_t = typename voider::type; - -namespace detail { -namespace swappable { - -using std::swap; - -template -struct is_swappable { - private: - template (), std::declval()))> - inline static std::true_type test(int); - - template - inline static std::false_type test(...); - - public: - static constexpr bool value = decltype(test(0))::value; -}; - -template -struct is_nothrow_swappable { - static constexpr bool value = - noexcept(swap(std::declval(), std::declval())); -}; - -template -struct is_nothrow_swappable : std::false_type {}; - -} // namespace swappable -} // namespace detail - -using detail::swappable::is_swappable; - -template -using is_nothrow_swappable = - detail::swappable::is_nothrow_swappable::value, T>; - -// -namespace detail { - -template -struct is_reference_wrapper : std::false_type {}; - -template -struct is_reference_wrapper> : std::true_type {}; - -template -struct Invoke; - -template <> -struct Invoke { - template - inline static constexpr auto invoke(R T::*pmf, Arg &&arg, Args &&...args) - MPARK_RETURN((lib::forward(arg).*pmf)(lib::forward(args)...)) -}; - -template <> -struct Invoke { - template - inline static constexpr auto invoke(R T::*pmf, Arg &&arg, Args &&...args) - MPARK_RETURN((lib::forward(arg).get().* - pmf)(lib::forward(args)...)) -}; - -template <> -struct Invoke { - template - inline static constexpr auto invoke(R T::*pmf, Arg &&arg, Args &&...args) - MPARK_RETURN(((*lib::forward(arg)).* - pmf)(lib::forward(args)...)) -}; - -template <> -struct Invoke { - template - inline static constexpr auto invoke(R T::*pmo, Arg &&arg) - MPARK_RETURN(lib::forward(arg).*pmo) -}; - -template <> -struct Invoke { - template - inline static constexpr auto invoke(R T::*pmo, Arg &&arg) - MPARK_RETURN(lib::forward(arg).get().*pmo) -}; - -template <> -struct Invoke { - template - inline static constexpr auto invoke(R T::*pmo, Arg &&arg) - MPARK_RETURN((*lib::forward(arg)).*pmo) -}; - -template -inline constexpr auto invoke(R T::*f, Arg &&arg, Args &&...args) - MPARK_RETURN(Invoke::value, - (std::is_base_of>::value ? 0 - : is_reference_wrapper>::value - ? 1 - : 2)>::invoke(f, - lib::forward(arg), - lib::forward(args)...)) - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4100) -#endif - template - inline constexpr auto invoke(F &&f, Args &&...args) - MPARK_RETURN(lib::forward(f)(lib::forward(args)...)) -#ifdef _MSC_VER -#pragma warning(pop) -#endif -} // namespace detail - -template -inline constexpr auto invoke(F &&f, Args &&...args) - MPARK_RETURN(detail::invoke(lib::forward(f), - lib::forward(args)...)) - - namespace detail { - template - struct invoke_result {}; - - template - struct invoke_result< - void_t(), std::declval()...))>, - F, - Args...> : identity(), - std::declval()...))> {}; - -} // namespace detail - -template -using invoke_result = detail::invoke_result; - -template -using invoke_result_t = typename invoke_result::type; - -namespace detail { - -template -struct is_invocable : std::false_type {}; - -template -struct is_invocable>, F, Args...> - : std::true_type {}; - -template -struct is_invocable_r : std::false_type {}; - -template -struct is_invocable_r>, R, F, Args...> - : std::is_convertible, R> {}; - -} // namespace detail - -template -using is_invocable = detail::is_invocable; - -template -using is_invocable_r = detail::is_invocable_r; - -namespace detail { - -template -struct is_nothrow_invocable { - static constexpr bool value = - noexcept(lib::invoke(std::declval(), std::declval()...)); -}; - -template -struct is_nothrow_invocable : std::false_type {}; - -template -struct is_nothrow_invocable_r { - private: - inline static R impl() { - return lib::invoke(std::declval(), std::declval()...); - } - - public: - static constexpr bool value = noexcept(impl()); -}; - -template -struct is_nothrow_invocable_r : std::false_type {}; - -} // namespace detail - -template -using is_nothrow_invocable = - detail::is_nothrow_invocable::value, F, Args...>; - -template -using is_nothrow_invocable_r = detail:: - is_nothrow_invocable_r::value, R, F, Args...>; - -// -#ifdef MPARK_BUILTIN_ADDRESSOF -template -inline constexpr T *addressof(T &arg) noexcept { - return __builtin_addressof(arg); -} -#else -namespace detail { - -namespace has_addressof_impl { - -struct fail; - -template -inline fail operator&(T &&); - -template -inline static constexpr bool impl() { - return (std::is_class::value || std::is_union::value) && - !std::is_same()), fail>::value; -} - -} // namespace has_addressof_impl - -template -using has_addressof = bool_constant()>; - -template -inline constexpr T *addressof(T &arg, std::true_type) noexcept { - return std::addressof(arg); -} - -template -inline constexpr T *addressof(T &arg, std::false_type) noexcept { - return &arg; -} - -} // namespace detail - -template -inline constexpr T *addressof(T &arg) noexcept { - return detail::addressof(arg, detail::has_addressof{}); -} -#endif - -template -inline constexpr T *addressof(const T &&) = delete; - -} // namespace cpp17 - -template -struct remove_all_extents : identity {}; - -template -struct remove_all_extents> : remove_all_extents {}; - -template -using remove_all_extents_t = typename remove_all_extents::type; - -template -using size_constant = std::integral_constant; - -template -struct indexed_type : size_constant { - using type = T; -}; - -template -using all = std::is_same, - integer_sequence>; - -#ifdef MPARK_TYPE_PACK_ELEMENT -template -using type_pack_element_t = __type_pack_element; -#else -template -struct type_pack_element_impl { - private: - template - struct set; - - template - struct set> : indexed_type... {}; - - template - inline static std::enable_if impl(indexed_type); - - inline static std::enable_if impl(...); - - public: - using type = decltype(impl(set>{})); -}; - -template -using type_pack_element = typename type_pack_element_impl::type; - -template -using type_pack_element_t = typename type_pack_element::type; -#endif - -#ifdef MPARK_TRIVIALITY_TYPE_TRAITS -using std::is_trivially_copy_assignable; -using std::is_trivially_copy_constructible; -using std::is_trivially_move_assignable; -using std::is_trivially_move_constructible; -#else -template -struct is_trivially_copy_constructible - : bool_constant::value &&__has_trivial_copy( - T)> {}; - -template -struct is_trivially_move_constructible : bool_constant<__is_trivial(T)> {}; - -template -struct is_trivially_copy_assignable - : bool_constant::value &&__has_trivial_assign( - T)> {}; - -template -struct is_trivially_move_assignable : bool_constant<__is_trivial(T)> {}; -#endif - -template -struct dependent_type : T {}; - -template -struct push_back; - -template -using push_back_t = typename push_back::type; - -template -struct push_back, J> { - using type = index_sequence; -}; - -} // namespace lib -} // namespace paddlenlp - -#undef MPARK_RETURN - -#endif // MPARK_LIB_HPP - -namespace paddlenlp { - -#ifdef MPARK_RETURN_TYPE_DEDUCTION - -#define AUTO auto -#define AUTO_RETURN(...) \ - { return __VA_ARGS__; } - -#define AUTO_REFREF auto && -#define AUTO_REFREF_RETURN(...) \ - { return __VA_ARGS__; } - -#define DECLTYPE_AUTO decltype(auto) -#define DECLTYPE_AUTO_RETURN(...) \ - { return __VA_ARGS__; } - -#else - -#define AUTO auto -#define AUTO_RETURN(...) \ - ->lib::decay_t { return __VA_ARGS__; } - -#define AUTO_REFREF auto -#define AUTO_REFREF_RETURN(...) \ - ->decltype((__VA_ARGS__)) { \ - static_assert(std::is_reference::value, ""); \ - return __VA_ARGS__; \ - } - -#define DECLTYPE_AUTO auto -#define DECLTYPE_AUTO_RETURN(...) \ - ->decltype(__VA_ARGS__) { return __VA_ARGS__; } - -#endif - -class bad_variant_access : public std::exception { - public: - virtual const char *what() const noexcept override { - return "bad_variant_access"; - } -}; - -[[noreturn]] inline void throw_bad_variant_access() { -#ifdef MPARK_EXCEPTIONS - throw bad_variant_access{}; -#else - std::terminate(); - MPARK_BUILTIN_UNREACHABLE; -#endif -} - -template -class variant; - -template -struct variant_size; - -#ifdef MPARK_VARIABLE_TEMPLATES -template -constexpr std::size_t variant_size_v = variant_size::value; -#endif - -template -struct variant_size : variant_size {}; - -template -struct variant_size : variant_size {}; - -template -struct variant_size : variant_size {}; - -template -struct variant_size> : lib::size_constant {}; - -template -struct variant_alternative; - -template -using variant_alternative_t = typename variant_alternative::type; - -template -struct variant_alternative - : std::add_const> {}; - -template -struct variant_alternative - : std::add_volatile> {}; - -template -struct variant_alternative - : std::add_cv> {}; - -template -struct variant_alternative> { - static_assert(I < sizeof...(Ts), - "index out of bounds in `std::variant_alternative<>`"); - using type = lib::type_pack_element_t; -}; - -constexpr std::size_t variant_npos = static_cast(-1); - -namespace detail { - -constexpr std::size_t not_found = static_cast(-1); -constexpr std::size_t ambiguous = static_cast(-2); - -#ifdef MPARK_CPP14_CONSTEXPR -template -inline constexpr std::size_t find_index() { - constexpr lib::array matches = { - {std::is_same::value...}}; - std::size_t result = not_found; - for (std::size_t i = 0; i < sizeof...(Ts); ++i) { - if (matches[i]) { - if (result != not_found) { - return ambiguous; - } - result = i; - } - } - return result; -} -#else -inline constexpr std::size_t find_index_impl(std::size_t result, std::size_t) { - return result; -} - -template -inline constexpr std::size_t find_index_impl(std::size_t result, - std::size_t idx, - bool b, - Bs... bs) { - return b ? (result != not_found ? ambiguous - : find_index_impl(idx, idx + 1, bs...)) - : find_index_impl(result, idx + 1, bs...); -} - -template -inline constexpr std::size_t find_index() { - return find_index_impl(not_found, 0, std::is_same::value...); -} -#endif - -template -using find_index_sfinae_impl = - lib::enable_if_t>; - -template -using find_index_sfinae = find_index_sfinae_impl()>; - -template -struct find_index_checked_impl : lib::size_constant { - static_assert(I != not_found, "the specified type is not found."); - static_assert(I != ambiguous, "the specified type is ambiguous."); -}; - -template -using find_index_checked = find_index_checked_impl()>; - -struct valueless_t {}; - -enum class Trait { TriviallyAvailable, Available, Unavailable }; - -template - class IsTriviallyAvailable, - template - class IsAvailable> -inline constexpr Trait trait() { - return IsTriviallyAvailable::value ? Trait::TriviallyAvailable - : IsAvailable::value ? Trait::Available - : Trait::Unavailable; -} - -#ifdef MPARK_CPP14_CONSTEXPR -template -inline constexpr Trait common_trait(Traits... traits_) { - Trait result = Trait::TriviallyAvailable; - lib::array traits = {{traits_...}}; - for (std::size_t i = 0; i < sizeof...(Traits); ++i) { - Trait t = traits[i]; - if (static_cast(t) > static_cast(result)) { - result = t; - } - } - return result; -} -#else -inline constexpr Trait common_trait_impl(Trait result) { return result; } - -template -inline constexpr Trait common_trait_impl(Trait result, Trait t, Traits... ts) { - return static_cast(t) > static_cast(result) - ? common_trait_impl(t, ts...) - : common_trait_impl(result, ts...); -} - -template -inline constexpr Trait common_trait(Traits... ts) { - return common_trait_impl(Trait::TriviallyAvailable, ts...); -} -#endif - -template -struct traits { - static constexpr Trait copy_constructible_trait = - common_trait(trait()...); - - static constexpr Trait move_constructible_trait = - common_trait(trait()...); - - static constexpr Trait copy_assignable_trait = - common_trait(copy_constructible_trait, - trait()...); - - static constexpr Trait move_assignable_trait = - common_trait(move_constructible_trait, - trait()...); - - static constexpr Trait destructible_trait = common_trait( - trait()...); -}; - -namespace access { - -struct recursive_union { -#ifdef MPARK_RETURN_TYPE_DEDUCTION - template - inline static constexpr auto &&get_alt(V &&v, in_place_index_t<0>) { - return lib::forward(v).head_; - } - - template - inline static constexpr auto &&get_alt(V &&v, in_place_index_t) { - return get_alt(lib::forward(v).tail_, in_place_index_t{}); - } -#else - template - struct get_alt_impl { - template - inline constexpr AUTO_REFREF operator()(V &&v) const - AUTO_REFREF_RETURN(get_alt_impl{}(lib::forward(v).tail_)) - }; - - template - struct get_alt_impl<0, Dummy> { - template - inline constexpr AUTO_REFREF operator()(V &&v) const - AUTO_REFREF_RETURN(lib::forward(v).head_) - }; - - template - inline static constexpr AUTO_REFREF get_alt(V &&v, in_place_index_t) - AUTO_REFREF_RETURN(get_alt_impl{}(lib::forward(v))) -#endif -}; - -struct base { - template - inline static constexpr AUTO_REFREF get_alt(V &&v) -#ifdef _MSC_VER - AUTO_REFREF_RETURN(recursive_union::get_alt(lib::forward(v).data_, - in_place_index_t{})) -#else - AUTO_REFREF_RETURN(recursive_union::get_alt(data(lib::forward(v)), - in_place_index_t{})) -#endif -}; - -struct variant { - template - inline static constexpr AUTO_REFREF get_alt(V &&v) - AUTO_REFREF_RETURN(base::get_alt(lib::forward(v).impl_)) -}; - -} // namespace access - -namespace visitation { - -#if defined(MPARK_CPP14_CONSTEXPR) && !defined(_MSC_VER) -#define MPARK_VARIANT_SWITCH_VISIT -#endif - -struct base { - template - using dispatch_result_t = - decltype(lib::invoke(std::declval(), - access::base::get_alt<0>(std::declval())...)); - - template - struct expected { - template - inline static constexpr bool but_got() { - return std::is_same::value; - } - }; - - template - struct visit_return_type_check { - static_assert(expected::template but_got(), - "`visit` requires the visitor to have a single return type"); - - template - inline static constexpr DECLTYPE_AUTO invoke(Visitor &&visitor, - Alts &&...alts) - DECLTYPE_AUTO_RETURN(lib::invoke(lib::forward(visitor), - lib::forward(alts)...)) - }; - -#ifdef MPARK_VARIANT_SWITCH_VISIT - template - struct dispatcher; - - template - struct dispatcher { - template - MPARK_ALWAYS_INLINE static constexpr R dispatch(F &&, - typename ITs::type &&..., - Vs &&...) { - MPARK_BUILTIN_UNREACHABLE; - } - - template - MPARK_ALWAYS_INLINE static constexpr R dispatch_case(F &&, Vs &&...) { - MPARK_BUILTIN_UNREACHABLE; - } - - template - MPARK_ALWAYS_INLINE static constexpr R dispatch_at(std::size_t, - F &&, - Vs &&...) { - MPARK_BUILTIN_UNREACHABLE; - } - }; - - template - struct dispatcher { - template - MPARK_ALWAYS_INLINE static constexpr R dispatch( - F &&f, typename ITs::type &&...visited_vs) { - using Expected = R; - using Actual = decltype(lib::invoke( - lib::forward(f), - access::base::get_alt( - lib::forward(visited_vs))...)); - return visit_return_type_check::invoke( - lib::forward(f), - access::base::get_alt( - lib::forward(visited_vs))...); - } - - template - MPARK_ALWAYS_INLINE static constexpr R dispatch( - F &&f, typename ITs::type &&...visited_vs, V &&v, Vs &&...vs) { -#define MPARK_DISPATCH(I) \ - dispatcher<(I < lib::decay_t::size()), \ - R, \ - ITs..., \ - lib::indexed_type>:: \ - template dispatch<0>(lib::forward(f), \ - lib::forward(visited_vs)..., \ - lib::forward(v), \ - lib::forward(vs)...) - -#define MPARK_DEFAULT(I) \ - dispatcher<(I < lib::decay_t::size()), R, ITs...>::template dispatch( \ - lib::forward(f), \ - lib::forward(visited_vs)..., \ - lib::forward(v), \ - lib::forward(vs)...) - - switch (v.index()) { - case B + 0: - return MPARK_DISPATCH(B + 0); - case B + 1: - return MPARK_DISPATCH(B + 1); - case B + 2: - return MPARK_DISPATCH(B + 2); - case B + 3: - return MPARK_DISPATCH(B + 3); - case B + 4: - return MPARK_DISPATCH(B + 4); - case B + 5: - return MPARK_DISPATCH(B + 5); - case B + 6: - return MPARK_DISPATCH(B + 6); - case B + 7: - return MPARK_DISPATCH(B + 7); - case B + 8: - return MPARK_DISPATCH(B + 8); - case B + 9: - return MPARK_DISPATCH(B + 9); - case B + 10: - return MPARK_DISPATCH(B + 10); - case B + 11: - return MPARK_DISPATCH(B + 11); - case B + 12: - return MPARK_DISPATCH(B + 12); - case B + 13: - return MPARK_DISPATCH(B + 13); - case B + 14: - return MPARK_DISPATCH(B + 14); - case B + 15: - return MPARK_DISPATCH(B + 15); - case B + 16: - return MPARK_DISPATCH(B + 16); - case B + 17: - return MPARK_DISPATCH(B + 17); - case B + 18: - return MPARK_DISPATCH(B + 18); - case B + 19: - return MPARK_DISPATCH(B + 19); - case B + 20: - return MPARK_DISPATCH(B + 20); - case B + 21: - return MPARK_DISPATCH(B + 21); - case B + 22: - return MPARK_DISPATCH(B + 22); - case B + 23: - return MPARK_DISPATCH(B + 23); - case B + 24: - return MPARK_DISPATCH(B + 24); - case B + 25: - return MPARK_DISPATCH(B + 25); - case B + 26: - return MPARK_DISPATCH(B + 26); - case B + 27: - return MPARK_DISPATCH(B + 27); - case B + 28: - return MPARK_DISPATCH(B + 28); - case B + 29: - return MPARK_DISPATCH(B + 29); - case B + 30: - return MPARK_DISPATCH(B + 30); - case B + 31: - return MPARK_DISPATCH(B + 31); - default: - return MPARK_DEFAULT(B + 32); - } - -#undef MPARK_DEFAULT -#undef MPARK_DISPATCH - } - - template - MPARK_ALWAYS_INLINE static constexpr R dispatch_case(F &&f, Vs &&...vs) { - using Expected = R; - using Actual = decltype(lib::invoke( - lib::forward(f), - access::base::get_alt(lib::forward(vs))...)); - return visit_return_type_check::invoke( - lib::forward(f), - access::base::get_alt(lib::forward(vs))...); - } - - template - MPARK_ALWAYS_INLINE static constexpr R dispatch_at(std::size_t index, - F &&f, - V &&v, - Vs &&...vs) { - static_assert(lib::all<(lib::decay_t::size() == - lib::decay_t::size())...>::value, - "all of the variants must be the same size."); -#define MPARK_DISPATCH_AT(I) \ - dispatcher<(I < lib::decay_t::size()), R>::template dispatch_case( \ - lib::forward(f), lib::forward(v), lib::forward(vs)...) - -#define MPARK_DEFAULT(I) \ - dispatcher<(I < lib::decay_t::size()), R>::template dispatch_at( \ - index, lib::forward(f), lib::forward(v), lib::forward(vs)...) - - switch (index) { - case B + 0: - return MPARK_DISPATCH_AT(B + 0); - case B + 1: - return MPARK_DISPATCH_AT(B + 1); - case B + 2: - return MPARK_DISPATCH_AT(B + 2); - case B + 3: - return MPARK_DISPATCH_AT(B + 3); - case B + 4: - return MPARK_DISPATCH_AT(B + 4); - case B + 5: - return MPARK_DISPATCH_AT(B + 5); - case B + 6: - return MPARK_DISPATCH_AT(B + 6); - case B + 7: - return MPARK_DISPATCH_AT(B + 7); - case B + 8: - return MPARK_DISPATCH_AT(B + 8); - case B + 9: - return MPARK_DISPATCH_AT(B + 9); - case B + 10: - return MPARK_DISPATCH_AT(B + 10); - case B + 11: - return MPARK_DISPATCH_AT(B + 11); - case B + 12: - return MPARK_DISPATCH_AT(B + 12); - case B + 13: - return MPARK_DISPATCH_AT(B + 13); - case B + 14: - return MPARK_DISPATCH_AT(B + 14); - case B + 15: - return MPARK_DISPATCH_AT(B + 15); - case B + 16: - return MPARK_DISPATCH_AT(B + 16); - case B + 17: - return MPARK_DISPATCH_AT(B + 17); - case B + 18: - return MPARK_DISPATCH_AT(B + 18); - case B + 19: - return MPARK_DISPATCH_AT(B + 19); - case B + 20: - return MPARK_DISPATCH_AT(B + 20); - case B + 21: - return MPARK_DISPATCH_AT(B + 21); - case B + 22: - return MPARK_DISPATCH_AT(B + 22); - case B + 23: - return MPARK_DISPATCH_AT(B + 23); - case B + 24: - return MPARK_DISPATCH_AT(B + 24); - case B + 25: - return MPARK_DISPATCH_AT(B + 25); - case B + 26: - return MPARK_DISPATCH_AT(B + 26); - case B + 27: - return MPARK_DISPATCH_AT(B + 27); - case B + 28: - return MPARK_DISPATCH_AT(B + 28); - case B + 29: - return MPARK_DISPATCH_AT(B + 29); - case B + 30: - return MPARK_DISPATCH_AT(B + 30); - case B + 31: - return MPARK_DISPATCH_AT(B + 31); - default: - return MPARK_DEFAULT(B + 32); - } - -#undef MPARK_DEFAULT -#undef MPARK_DISPATCH_AT - } - }; -#else - template - inline static constexpr const T &at(const T &elem) noexcept { - return elem; - } - - template - inline static constexpr const lib::remove_all_extents_t &at( - const lib::array &elems, std::size_t i, Is... is) noexcept { - return at(elems[i], is...); - } - - template - inline static constexpr lib::array, sizeof...(Fs) + 1> - make_farray(F &&f, Fs &&...fs) { - return {{lib::forward(f), lib::forward(fs)...}}; - } - - template - struct make_fmatrix_impl { - template - inline static constexpr dispatch_result_t dispatch(F &&f, - Vs &&...vs) { - using Expected = dispatch_result_t; - using Actual = decltype(lib::invoke( - lib::forward(f), - access::base::get_alt(lib::forward(vs))...)); - return visit_return_type_check::invoke( - lib::forward(f), - access::base::get_alt(lib::forward(vs))...); - } - -#ifdef MPARK_RETURN_TYPE_DEDUCTION - template - inline static constexpr auto impl(lib::index_sequence) { - return &dispatch; - } - - template - inline static constexpr auto impl(Is, - lib::index_sequence, - Ls... ls) { - return make_farray(impl(lib::push_back_t{}, ls...)...); - } -#else - template - struct impl; - - template - struct impl> { - inline constexpr AUTO operator()() const AUTO_RETURN(&dispatch) - }; - - template - struct impl, Ls...> { - inline constexpr AUTO operator()() const - AUTO_RETURN(make_farray(impl, Ls...>{}()...)) - }; -#endif - }; - -#ifdef MPARK_RETURN_TYPE_DEDUCTION - template - inline static constexpr auto make_fmatrix() { - return make_fmatrix_impl::impl( - lib::index_sequence<>{}, - lib::make_index_sequence::size()>{}...); - } -#else - template - inline static constexpr AUTO make_fmatrix() - AUTO_RETURN(typename make_fmatrix_impl::template impl< - lib::index_sequence<>, - lib::make_index_sequence::size()>...>{}()) -#endif - - template - struct make_fdiagonal_impl { - template - inline static constexpr dispatch_result_t dispatch(F &&f, - Vs &&...vs) { - using Expected = dispatch_result_t; - using Actual = decltype(lib::invoke( - lib::forward(f), - access::base::get_alt(lib::forward(vs))...)); - return visit_return_type_check::invoke( - lib::forward(f), - access::base::get_alt(lib::forward(vs))...); - } - - template - inline static constexpr AUTO impl(lib::index_sequence) - AUTO_RETURN(make_farray(&dispatch...)) - }; - - template - inline static constexpr auto make_fdiagonal() - -> decltype(make_fdiagonal_impl::impl( - lib::make_index_sequence::size()>{})) { - static_assert(lib::all<(lib::decay_t::size() == - lib::decay_t::size())...>::value, - "all of the variants must be the same size."); - return make_fdiagonal_impl::impl( - lib::make_index_sequence::size()>{}); - } -#endif -}; - -#if !defined(MPARK_VARIANT_SWITCH_VISIT) && \ - (!defined(_MSC_VER) || _MSC_VER >= 1910) -template -using fmatrix_t = decltype(base::make_fmatrix()); - -template -struct fmatrix { - static constexpr fmatrix_t value = base::make_fmatrix(); -}; - -template -constexpr fmatrix_t fmatrix::value; - -template -using fdiagonal_t = decltype(base::make_fdiagonal()); - -template -struct fdiagonal { - static constexpr fdiagonal_t value = - base::make_fdiagonal(); -}; - -template -constexpr fdiagonal_t fdiagonal::value; -#endif - -struct alt { - template - inline static constexpr DECLTYPE_AUTO visit_alt(Visitor &&visitor, Vs &&...vs) -#ifdef MPARK_VARIANT_SWITCH_VISIT - DECLTYPE_AUTO_RETURN( - base::dispatcher(vs)))...>>:: - template dispatch<0>(lib::forward(visitor), - as_base(lib::forward(vs))...)) -#elif !defined(_MSC_VER) || _MSC_VER >= 1910 - DECLTYPE_AUTO_RETURN( - base::at(fmatrix(vs)))...>::value, - vs.index()...)(lib::forward(visitor), - as_base(lib::forward(vs))...)) -#else - DECLTYPE_AUTO_RETURN(base::at( - base::make_fmatrix(vs)))...>(), - vs.index()...)(lib::forward(visitor), - as_base(lib::forward(vs))...)) -#endif - - template - inline static constexpr DECLTYPE_AUTO - visit_alt_at(std::size_t index, Visitor &&visitor, Vs &&...vs) -#ifdef MPARK_VARIANT_SWITCH_VISIT - DECLTYPE_AUTO_RETURN( - base::dispatcher< - true, - base::dispatch_result_t< - Visitor, - decltype(as_base(lib::forward(vs)))...>>:: - template dispatch_at<0>(index, - lib::forward(visitor), - as_base(lib::forward(vs))...)) -#elif !defined(_MSC_VER) || _MSC_VER >= 1910 - DECLTYPE_AUTO_RETURN(base::at( - fdiagonal(vs)))...>::value, - index)(lib::forward(visitor), - as_base(lib::forward(vs))...)) -#else - DECLTYPE_AUTO_RETURN( - base::at(base::make_fdiagonal< - Visitor &&, - decltype(as_base(lib::forward(vs)))...>(), - index)(lib::forward(visitor), - as_base(lib::forward(vs))...)) -#endif -}; - -struct variant { - private: - template - struct visitor { - template - inline static constexpr bool does_not_handle() { - return lib::is_invocable::value; - } - }; - - template - struct visit_exhaustiveness_check { - static_assert(visitor::template does_not_handle(), - "`visit` requires the visitor to be exhaustive."); - - inline static constexpr DECLTYPE_AUTO invoke(Visitor &&visitor, - Values &&...values) - DECLTYPE_AUTO_RETURN(lib::invoke(lib::forward(visitor), - lib::forward(values)...)) - }; - - template - struct value_visitor { - Visitor &&visitor_; - - template - inline constexpr DECLTYPE_AUTO operator()(Alts &&...alts) const - DECLTYPE_AUTO_RETURN(visit_exhaustiveness_check< - Visitor, - decltype((lib::forward(alts).value))...>:: - invoke(lib::forward(visitor_), - lib::forward(alts).value...)) - }; - - template - inline static constexpr AUTO make_value_visitor(Visitor &&visitor) - AUTO_RETURN(value_visitor{lib::forward(visitor)}) - - public - : template - inline static constexpr DECLTYPE_AUTO - visit_alt(Visitor &&visitor, Vs &&...vs) - DECLTYPE_AUTO_RETURN(alt::visit_alt(lib::forward(visitor), - lib::forward(vs).impl_...)) - - template - inline static constexpr DECLTYPE_AUTO - visit_alt_at(std::size_t index, Visitor &&visitor, Vs &&...vs) - DECLTYPE_AUTO_RETURN( - alt::visit_alt_at(index, - lib::forward(visitor), - lib::forward(vs).impl_...)) - - template - inline static constexpr DECLTYPE_AUTO - visit_value(Visitor &&visitor, Vs &&...vs) DECLTYPE_AUTO_RETURN( - visit_alt(make_value_visitor(lib::forward(visitor)), - lib::forward(vs)...)) - - template - inline static constexpr DECLTYPE_AUTO - visit_value_at(std::size_t index, Visitor &&visitor, Vs &&...vs) - DECLTYPE_AUTO_RETURN( - visit_alt_at(index, - make_value_visitor(lib::forward(visitor)), - lib::forward(vs)...)) -}; - -} // namespace visitation - -template -struct alt { - using value_type = T; - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4244) -#endif - template - inline explicit constexpr alt(in_place_t, Args &&...args) - : value(lib::forward(args)...) {} -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - T value; -}; - -template -union recursive_union; - -template -union recursive_union {}; - -#define MPARK_VARIANT_RECURSIVE_UNION(destructible_trait, destructor) \ - template \ - union recursive_union { \ - public: \ - inline explicit constexpr recursive_union(valueless_t) noexcept \ - : dummy_{} {} \ - \ - template \ - inline explicit constexpr recursive_union(in_place_index_t<0>, \ - Args &&...args) \ - : head_(in_place_t{}, lib::forward(args)...) {} \ - \ - template \ - inline explicit constexpr recursive_union(in_place_index_t, \ - Args &&...args) \ - : tail_(in_place_index_t{}, lib::forward(args)...) {} \ - \ - recursive_union(const recursive_union &) = default; \ - recursive_union(recursive_union &&) = default; \ - \ - destructor \ - \ - recursive_union & \ - operator=(const recursive_union &) = default; \ - recursive_union &operator=(recursive_union &&) = default; \ - \ - private: \ - char dummy_; \ - alt head_; \ - recursive_union tail_; \ - \ - friend struct access::recursive_union; \ - } - -MPARK_VARIANT_RECURSIVE_UNION(Trait::TriviallyAvailable, - ~recursive_union() = default;); -MPARK_VARIANT_RECURSIVE_UNION(Trait::Available, ~recursive_union(){}); -MPARK_VARIANT_RECURSIVE_UNION(Trait::Unavailable, ~recursive_union() = delete;); - -#undef MPARK_VARIANT_RECURSIVE_UNION - -using index_t = unsigned int; - -template -class base { - public: - inline explicit constexpr base(valueless_t tag) noexcept - : data_(tag), index_(static_cast(-1)) {} - - template - inline explicit constexpr base(in_place_index_t, Args &&...args) - : data_(in_place_index_t{}, lib::forward(args)...), index_(I) {} - - inline constexpr bool valueless_by_exception() const noexcept { - return index_ == static_cast(-1); - } - - inline constexpr std::size_t index() const noexcept { - return valueless_by_exception() ? variant_npos : index_; - } - - protected: - using data_t = recursive_union; - - friend inline constexpr base &as_base(base &b) { return b; } - friend inline constexpr const base &as_base(const base &b) { return b; } - friend inline constexpr base &&as_base(base &&b) { return lib::move(b); } - friend inline constexpr const base &&as_base(const base &&b) { - return lib::move(b); - } - - friend inline constexpr data_t &data(base &b) { return b.data_; } - friend inline constexpr const data_t &data(const base &b) { return b.data_; } - friend inline constexpr data_t &&data(base &&b) { return lib::move(b).data_; } - friend inline constexpr const data_t &&data(const base &&b) { - return lib::move(b).data_; - } - - inline static constexpr std::size_t size() { return sizeof...(Ts); } - - data_t data_; - index_t index_; - - friend struct access::base; - friend struct visitation::base; -}; - -struct dtor { -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4100) -#endif - template - inline void operator()(Alt &alt) const noexcept { - alt.~Alt(); - } -#ifdef _MSC_VER -#pragma warning(pop) -#endif -}; - -#if !defined(_MSC_VER) || _MSC_VER >= 1910 -#define MPARK_INHERITING_CTOR(type, base) using base::base; -#else -#define MPARK_INHERITING_CTOR(type, base) \ - template \ - inline explicit constexpr type(Args &&...args) \ - : base(lib::forward(args)...) {} -#endif - -template -class destructor; - -#define MPARK_VARIANT_DESTRUCTOR(destructible_trait, definition, destroy) \ - template \ - class destructor, destructible_trait> \ - : public base { \ - using super = base; \ - \ - public: \ - MPARK_INHERITING_CTOR(destructor, super) \ - using super::operator=; \ - \ - destructor(const destructor &) = default; \ - destructor(destructor &&) = default; \ - definition destructor &operator=(const destructor &) = default; \ - destructor &operator=(destructor &&) = default; \ - \ - protected: \ - destroy \ - } - -MPARK_VARIANT_DESTRUCTOR( - Trait::TriviallyAvailable, ~destructor() = default; - , inline void destroy() noexcept { - this->index_ = static_cast(-1); - }); - -MPARK_VARIANT_DESTRUCTOR( - Trait::Available, - ~destructor() { destroy(); }, - inline void destroy() noexcept { - if (!this->valueless_by_exception()) { - visitation::alt::visit_alt(dtor{}, *this); - } - this->index_ = static_cast(-1); - }); - -MPARK_VARIANT_DESTRUCTOR(Trait::Unavailable, ~destructor() = delete; - , inline void destroy() noexcept = delete;); - -#undef MPARK_VARIANT_DESTRUCTOR - -template -class constructor : public destructor { - using super = destructor; - - public: - MPARK_INHERITING_CTOR(constructor, super) - using super::operator=; - - protected: -#ifndef MPARK_GENERIC_LAMBDAS - struct ctor { - template - inline void operator()(LhsAlt &lhs_alt, RhsAlt &&rhs_alt) const { - constructor::construct_alt(lhs_alt, lib::forward(rhs_alt).value); - } - }; -#endif - - template - inline static T &construct_alt(alt &a, Args &&...args) { - auto *result = ::new (static_cast(lib::addressof(a))) - alt(in_place_t{}, lib::forward(args)...); - return result->value; - } - - template - inline static void generic_construct(constructor &lhs, Rhs &&rhs) { - lhs.destroy(); - if (!rhs.valueless_by_exception()) { - visitation::alt::visit_alt_at( - rhs.index(), -#ifdef MPARK_GENERIC_LAMBDAS - [](auto &lhs_alt, auto &&rhs_alt) { - constructor::construct_alt( - lhs_alt, lib::forward(rhs_alt).value); - } -#else - ctor {} -#endif - , - lhs, - lib::forward(rhs)); - lhs.index_ = rhs.index_; - } - } -}; - -template -class move_constructor; - -#define MPARK_VARIANT_MOVE_CONSTRUCTOR(move_constructible_trait, definition) \ - template \ - class move_constructor, move_constructible_trait> \ - : public constructor> { \ - using super = constructor>; \ - \ - public: \ - MPARK_INHERITING_CTOR(move_constructor, super) \ - using super::operator=; \ - \ - move_constructor(const move_constructor &) = default; \ - definition ~move_constructor() = default; \ - move_constructor &operator=(const move_constructor &) = default; \ - move_constructor &operator=(move_constructor &&) = default; \ - } - -MPARK_VARIANT_MOVE_CONSTRUCTOR( - Trait::TriviallyAvailable, - move_constructor(move_constructor &&that) = default;); - -MPARK_VARIANT_MOVE_CONSTRUCTOR( - Trait::Available, - move_constructor(move_constructor &&that) noexcept( - lib::all::value...>::value) - : move_constructor(valueless_t{}) { - this->generic_construct(*this, lib::move(that)); - }); - -MPARK_VARIANT_MOVE_CONSTRUCTOR(Trait::Unavailable, - move_constructor(move_constructor &&) = delete;); - -#undef MPARK_VARIANT_MOVE_CONSTRUCTOR - -template -class copy_constructor; - -#define MPARK_VARIANT_COPY_CONSTRUCTOR(copy_constructible_trait, definition) \ - template \ - class copy_constructor, copy_constructible_trait> \ - : public move_constructor> { \ - using super = move_constructor>; \ - \ - public: \ - MPARK_INHERITING_CTOR(copy_constructor, super) \ - using super::operator=; \ - \ - definition copy_constructor(copy_constructor &&) = default; \ - ~copy_constructor() = default; \ - copy_constructor &operator=(const copy_constructor &) = default; \ - copy_constructor &operator=(copy_constructor &&) = default; \ - } - -MPARK_VARIANT_COPY_CONSTRUCTOR( - Trait::TriviallyAvailable, - copy_constructor(const copy_constructor &that) = default;); - -MPARK_VARIANT_COPY_CONSTRUCTOR( - Trait::Available, copy_constructor(const copy_constructor &that) - : copy_constructor(valueless_t{}) { - this->generic_construct(*this, that); - }); - -MPARK_VARIANT_COPY_CONSTRUCTOR( - Trait::Unavailable, copy_constructor(const copy_constructor &) = delete;); - -#undef MPARK_VARIANT_COPY_CONSTRUCTOR - -template -class assignment : public copy_constructor { - using super = copy_constructor; - - public: - MPARK_INHERITING_CTOR(assignment, super) - using super::operator=; - - template - inline /* auto & */ auto emplace(Args &&...args) - -> decltype(this->construct_alt(access::base::get_alt(*this), - lib::forward(args)...)) { - this->destroy(); - auto &result = this->construct_alt(access::base::get_alt(*this), - lib::forward(args)...); - this->index_ = I; - return result; - } - - protected: -#ifndef MPARK_GENERIC_LAMBDAS - template - struct assigner { - template - inline void operator()(ThisAlt &this_alt, ThatAlt &&that_alt) const { - self->assign_alt(this_alt, lib::forward(that_alt).value); - } - assignment *self; - }; -#endif - - template - inline void assign_alt(alt &a, Arg &&arg) { - if (this->index() == I) { -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4244) -#endif - a.value = lib::forward(arg); -#ifdef _MSC_VER -#pragma warning(pop) -#endif - } else { - struct { - void operator()(std::true_type) const { - this_->emplace(lib::forward(arg_)); - } - void operator()(std::false_type) const { - this_->emplace(T(lib::forward(arg_))); - } - assignment *this_; - Arg &&arg_; - } impl{this, lib::forward(arg)}; - impl(lib::bool_constant < std::is_nothrow_constructible::value || - !std::is_nothrow_move_constructible::value > {}); - } - } - - template - inline void generic_assign(That &&that) { - if (this->valueless_by_exception() && that.valueless_by_exception()) { - // do nothing. - } else if (that.valueless_by_exception()) { - this->destroy(); - } else { - visitation::alt::visit_alt_at( - that.index(), -#ifdef MPARK_GENERIC_LAMBDAS - [this](auto &this_alt, auto &&that_alt) { - this->assign_alt(this_alt, - lib::forward(that_alt).value); - } -#else - assigner { this } -#endif - , - *this, - lib::forward(that)); - } - } -}; - -template -class move_assignment; - -#define MPARK_VARIANT_MOVE_ASSIGNMENT(move_assignable_trait, definition) \ - template \ - class move_assignment, move_assignable_trait> \ - : public assignment> { \ - using super = assignment>; \ - \ - public: \ - MPARK_INHERITING_CTOR(move_assignment, super) \ - using super::operator=; \ - \ - move_assignment(const move_assignment &) = default; \ - move_assignment(move_assignment &&) = default; \ - ~move_assignment() = default; \ - move_assignment &operator=(const move_assignment &) = default; \ - definition \ - } - -MPARK_VARIANT_MOVE_ASSIGNMENT( - Trait::TriviallyAvailable, - move_assignment &operator=(move_assignment &&that) = default;); - -MPARK_VARIANT_MOVE_ASSIGNMENT( - Trait::Available, - move_assignment & - operator=(move_assignment &&that) noexcept( - lib::all<(std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value)...>::value) { - this->generic_assign(lib::move(that)); - return *this; - }); - -MPARK_VARIANT_MOVE_ASSIGNMENT( - Trait::Unavailable, - move_assignment &operator=(move_assignment &&) = delete;); - -#undef MPARK_VARIANT_MOVE_ASSIGNMENT - -template -class copy_assignment; - -#define MPARK_VARIANT_COPY_ASSIGNMENT(copy_assignable_trait, definition) \ - template \ - class copy_assignment, copy_assignable_trait> \ - : public move_assignment> { \ - using super = move_assignment>; \ - \ - public: \ - MPARK_INHERITING_CTOR(copy_assignment, super) \ - using super::operator=; \ - \ - copy_assignment(const copy_assignment &) = default; \ - copy_assignment(copy_assignment &&) = default; \ - ~copy_assignment() = default; \ - definition copy_assignment &operator=(copy_assignment &&) = default; \ - } - -MPARK_VARIANT_COPY_ASSIGNMENT( - Trait::TriviallyAvailable, - copy_assignment &operator=(const copy_assignment &that) = default;); - -MPARK_VARIANT_COPY_ASSIGNMENT( - Trait::Available, copy_assignment &operator=(const copy_assignment &that) { - this->generic_assign(that); - return *this; - }); - -MPARK_VARIANT_COPY_ASSIGNMENT( - Trait::Unavailable, - copy_assignment &operator=(const copy_assignment &) = delete;); - -#undef MPARK_VARIANT_COPY_ASSIGNMENT - -template -class impl : public copy_assignment> { - using super = copy_assignment>; - - public: - MPARK_INHERITING_CTOR(impl, super) - using super::operator=; - - template - inline void assign(Arg &&arg) { - this->assign_alt(access::base::get_alt(*this), lib::forward(arg)); - } - - inline void swap(impl &that) { - if (this->valueless_by_exception() && that.valueless_by_exception()) { - // do nothing. - } else if (this->index() == that.index()) { - visitation::alt::visit_alt_at( - this->index(), -#ifdef MPARK_GENERIC_LAMBDAS - [](auto &this_alt, auto &that_alt) { - using std::swap; - swap(this_alt.value, that_alt.value); - } -#else - swapper {} -#endif - , - *this, - that); - } else { - impl *lhs = this; - impl *rhs = lib::addressof(that); - if (lhs->move_nothrow() && !rhs->move_nothrow()) { - std::swap(lhs, rhs); - } - impl tmp(lib::move(*rhs)); -#ifdef MPARK_EXCEPTIONS - // EXTENSION: When the move construction of `lhs` into `rhs` throws - // and `tmp` is nothrow move constructible then we move `tmp` back - // into `rhs` and provide the strong exception safety guarantee. - try { - this->generic_construct(*rhs, lib::move(*lhs)); - } catch (...) { - if (tmp.move_nothrow()) { - this->generic_construct(*rhs, lib::move(tmp)); - } - throw; - } -#else - this->generic_construct(*rhs, lib::move(*lhs)); -#endif - this->generic_construct(*lhs, lib::move(tmp)); - } - } - - inline const std::type_info &type() const { - return visitation::alt::visit_alt_at( - this->index(), -#ifdef MPARK_GENERIC_LAMBDAS - [](auto &alt) -> const std::type_info & { return typeid(alt.value); } -#else - typer {} -#endif - , - *this); - } - - private: -#ifndef MPARK_GENERIC_LAMBDAS - struct swapper { - template - inline void operator()(ThisAlt &this_alt, ThatAlt &that_alt) const { - using std::swap; - swap(this_alt.value, that_alt.value); - } - }; - - struct typer { - template - inline const std::type_info &operator()(Alt &alt) const { - return typeid(alt.value); - } - }; -#endif - - inline constexpr bool move_nothrow() const { - return this->valueless_by_exception() || - lib::array{{std::is_nothrow_move_constructible< - Ts>::value...}}[this->index()]; - } -}; - -#undef MPARK_INHERITING_CTOR - -template -struct overload_leaf { - using F = lib::size_constant (*)(T); - operator F() const { return nullptr; } -}; - -template -struct overload_impl { - private: - template - struct impl; - - template - struct impl> : overload_leaf... {}; - - public: - using type = impl>; -}; - -template -using overload = typename overload_impl::type; - -template -using best_match = lib::invoke_result_t, T &&>; - -template -struct is_in_place_index : std::false_type {}; - -template -struct is_in_place_index> : std::true_type {}; - -template -struct is_in_place_type : std::false_type {}; - -template -struct is_in_place_type> : std::true_type {}; - -} // namespace detail - -template -class variant { - static_assert(0 < sizeof...(Ts), - "variant must consist of at least one alternative."); - - static_assert(lib::all::value...>::value, - "variant can not have an array type as an alternative."); - - static_assert(lib::all::value...>::value, - "variant can not have a reference type as an alternative."); - - static_assert(lib::all::value...>::value, - "variant can not have a void type as an alternative."); - - public: - template < - typename Front = lib::type_pack_element_t<0, Ts...>, - lib::enable_if_t::value, int> = 0> - inline constexpr variant() noexcept( - std::is_nothrow_default_constructible::value) - : impl_(in_place_index_t<0>{}) {} - - variant(const variant &) = default; - variant(variant &&) = default; - - template < - typename Arg, - typename Decayed = lib::decay_t, - lib::enable_if_t::value, int> = 0, - lib::enable_if_t::value, int> = 0, - lib::enable_if_t::value, int> = 0, - std::size_t I = detail::best_match::value, - typename T = lib::type_pack_element_t, - lib::enable_if_t::value, int> = 0> - inline constexpr variant(Arg &&arg) noexcept( - std::is_nothrow_constructible::value) - : impl_(in_place_index_t{}, lib::forward(arg)) {} - - template , - lib::enable_if_t::value, int> = 0> - inline explicit constexpr variant( - in_place_index_t, - Args &&...args) noexcept(std::is_nothrow_constructible::value) - : impl_(in_place_index_t{}, lib::forward(args)...) {} - - template < - std::size_t I, - typename Up, - typename... Args, - typename T = lib::type_pack_element_t, - lib::enable_if_t< - std::is_constructible &, Args...>::value, - int> = 0> - inline explicit constexpr variant( - in_place_index_t, - std::initializer_list il, - Args &&...args) noexcept(std:: - is_nothrow_constructible< - T, - std::initializer_list &, - Args...>::value) - : impl_(in_place_index_t{}, il, lib::forward(args)...) {} - - template ::value, - lib::enable_if_t::value, int> = 0> - inline explicit constexpr variant( - in_place_type_t, - Args &&...args) noexcept(std::is_nothrow_constructible::value) - : impl_(in_place_index_t{}, lib::forward(args)...) {} - - template < - typename T, - typename Up, - typename... Args, - std::size_t I = detail::find_index_sfinae::value, - lib::enable_if_t< - std::is_constructible &, Args...>::value, - int> = 0> - inline explicit constexpr variant( - in_place_type_t, - std::initializer_list il, - Args &&...args) noexcept(std:: - is_nothrow_constructible< - T, - std::initializer_list &, - Args...>::value) - : impl_(in_place_index_t{}, il, lib::forward(args)...) {} - - ~variant() = default; - - variant &operator=(const variant &) = default; - variant &operator=(variant &&) = default; - - template , variant>::value, - int> = 0, - std::size_t I = detail::best_match::value, - typename T = lib::type_pack_element_t, - lib::enable_if_t<(std::is_assignable::value && - std::is_constructible::value), - int> = 0> - inline variant &operator=(Arg &&arg) noexcept( - (std::is_nothrow_assignable::value && - std::is_nothrow_constructible::value)) { - impl_.template assign(lib::forward(arg)); - return *this; - } - - template , - lib::enable_if_t::value, int> = 0> - inline T &emplace(Args &&...args) { - return impl_.template emplace(lib::forward(args)...); - } - - template < - std::size_t I, - typename Up, - typename... Args, - typename T = lib::type_pack_element_t, - lib::enable_if_t< - std::is_constructible &, Args...>::value, - int> = 0> - inline T &emplace(std::initializer_list il, Args &&...args) { - return impl_.template emplace(il, lib::forward(args)...); - } - - template ::value, - lib::enable_if_t::value, int> = 0> - inline T &emplace(Args &&...args) { - return impl_.template emplace(lib::forward(args)...); - } - - template < - typename T, - typename Up, - typename... Args, - std::size_t I = detail::find_index_sfinae::value, - lib::enable_if_t< - std::is_constructible &, Args...>::value, - int> = 0> - inline T &emplace(std::initializer_list il, Args &&...args) { - return impl_.template emplace(il, lib::forward(args)...); - } - - inline constexpr bool valueless_by_exception() const noexcept { - return impl_.valueless_by_exception(); - } - - inline constexpr std::size_t index() const noexcept { return impl_.index(); } - - template , - Dummy>::value && - lib::dependent_type, - Dummy>::value)...>::value, - int> = 0> - inline void swap(variant &that) noexcept( - lib::all<(std::is_nothrow_move_constructible::value && - lib::is_nothrow_swappable::value)...>::value) { - impl_.swap(that.impl_); - } - - inline const std::type_info &type() const noexcept { return impl_.type(); } - - private: - detail::impl impl_; - - friend struct detail::access::variant; - friend struct detail::visitation::variant; -}; - -template -inline constexpr bool holds_alternative(const variant &v) noexcept { - return v.index() == I; -} - -template -inline constexpr bool holds_alternative(const variant &v) noexcept { - return holds_alternative::value>(v); -} - -namespace detail { -template -struct generic_get_impl { - constexpr generic_get_impl(int) noexcept {} - - constexpr AUTO_REFREF operator()(V &&v) const - AUTO_REFREF_RETURN(access::variant::get_alt(lib::forward(v)).value) -}; - -template -inline constexpr AUTO_REFREF generic_get(V &&v) - AUTO_REFREF_RETURN(generic_get_impl(holds_alternative(v) - ? 0 - : (throw_bad_variant_access(), - 0))(lib::forward(v))) -} // namespace detail - -template -inline constexpr variant_alternative_t> &get( - variant &v) { - return detail::generic_get(v); -} - -template -inline constexpr variant_alternative_t> &&get( - variant &&v) { - return detail::generic_get(lib::move(v)); -} - -template -inline constexpr const variant_alternative_t> &get( - const variant &v) { - return detail::generic_get(v); -} - -template -inline constexpr const variant_alternative_t> &&get( - const variant &&v) { - return detail::generic_get(lib::move(v)); -} - -template -inline constexpr T &get(variant &v) { - return get::value>(v); -} - -template -inline constexpr T &&get(variant &&v) { - return get::value>(lib::move(v)); -} - -template -inline constexpr const T &get(const variant &v) { - return get::value>(v); -} - -template -inline constexpr const T &&get(const variant &&v) { - return get::value>(lib::move(v)); -} - -namespace detail { - -template -inline constexpr /* auto * */ AUTO generic_get_if(V *v) noexcept - AUTO_RETURN(v &&holds_alternative(*v) - ? lib::addressof(access::variant::get_alt(*v).value) - : nullptr) - -} // namespace detail - -template -inline constexpr lib::add_pointer_t>> -get_if(variant *v) noexcept { - return detail::generic_get_if(v); -} - -template -inline constexpr lib::add_pointer_t< - const variant_alternative_t>> -get_if(const variant *v) noexcept { - return detail::generic_get_if(v); -} - -template -inline constexpr lib::add_pointer_t get_if(variant *v) noexcept { - return get_if::value>(v); -} - -template -inline constexpr lib::add_pointer_t get_if( - const variant *v) noexcept { - return get_if::value>(v); -} - -namespace detail { -template -struct convert_to_bool { - template - inline constexpr bool operator()(Lhs &&lhs, Rhs &&rhs) const { - static_assert( - std::is_convertible, bool>::value, - "relational operators must return a type" - " implicitly convertible to bool"); - return lib::invoke(RelOp{}, lib::forward(lhs), lib::forward(rhs)); - } -}; -} // namespace detail - -template -inline constexpr bool operator==(const variant &lhs, - const variant &rhs) { - using detail::visitation::variant; - using equal_to = detail::convert_to_bool; -#ifdef MPARK_CPP14_CONSTEXPR - if (lhs.index() != rhs.index()) return false; - if (lhs.valueless_by_exception()) return true; - return variant::visit_value_at(lhs.index(), equal_to{}, lhs, rhs); -#else - return lhs.index() == rhs.index() && - (lhs.valueless_by_exception() || - variant::visit_value_at(lhs.index(), equal_to{}, lhs, rhs)); -#endif -} - -template -inline constexpr bool operator!=(const variant &lhs, - const variant &rhs) { - using detail::visitation::variant; - using not_equal_to = detail::convert_to_bool; -#ifdef MPARK_CPP14_CONSTEXPR - if (lhs.index() != rhs.index()) return true; - if (lhs.valueless_by_exception()) return false; - return variant::visit_value_at(lhs.index(), not_equal_to{}, lhs, rhs); -#else - return lhs.index() != rhs.index() || - (!lhs.valueless_by_exception() && - variant::visit_value_at(lhs.index(), not_equal_to{}, lhs, rhs)); -#endif -} - -template -inline constexpr bool operator<(const variant &lhs, - const variant &rhs) { - using detail::visitation::variant; - using less = detail::convert_to_bool; -#ifdef MPARK_CPP14_CONSTEXPR - if (rhs.valueless_by_exception()) return false; - if (lhs.valueless_by_exception()) return true; - if (lhs.index() < rhs.index()) return true; - if (lhs.index() > rhs.index()) return false; - return variant::visit_value_at(lhs.index(), less{}, lhs, rhs); -#else - return !rhs.valueless_by_exception() && - (lhs.valueless_by_exception() || lhs.index() < rhs.index() || - (lhs.index() == rhs.index() && - variant::visit_value_at(lhs.index(), less{}, lhs, rhs))); -#endif -} - -template -inline constexpr bool operator>(const variant &lhs, - const variant &rhs) { - using detail::visitation::variant; - using greater = detail::convert_to_bool; -#ifdef MPARK_CPP14_CONSTEXPR - if (lhs.valueless_by_exception()) return false; - if (rhs.valueless_by_exception()) return true; - if (lhs.index() > rhs.index()) return true; - if (lhs.index() < rhs.index()) return false; - return variant::visit_value_at(lhs.index(), greater{}, lhs, rhs); -#else - return !lhs.valueless_by_exception() && - (rhs.valueless_by_exception() || lhs.index() > rhs.index() || - (lhs.index() == rhs.index() && - variant::visit_value_at(lhs.index(), greater{}, lhs, rhs))); -#endif -} - -template -inline constexpr bool operator<=(const variant &lhs, - const variant &rhs) { - using detail::visitation::variant; - using less_equal = detail::convert_to_bool; -#ifdef MPARK_CPP14_CONSTEXPR - if (lhs.valueless_by_exception()) return true; - if (rhs.valueless_by_exception()) return false; - if (lhs.index() < rhs.index()) return true; - if (lhs.index() > rhs.index()) return false; - return variant::visit_value_at(lhs.index(), less_equal{}, lhs, rhs); -#else - return lhs.valueless_by_exception() || - (!rhs.valueless_by_exception() && - (lhs.index() < rhs.index() || - (lhs.index() == rhs.index() && - variant::visit_value_at(lhs.index(), less_equal{}, lhs, rhs)))); -#endif -} - -template -inline constexpr bool operator>=(const variant &lhs, - const variant &rhs) { - using detail::visitation::variant; - using greater_equal = detail::convert_to_bool; -#ifdef MPARK_CPP14_CONSTEXPR - if (rhs.valueless_by_exception()) return true; - if (lhs.valueless_by_exception()) return false; - if (lhs.index() > rhs.index()) return true; - if (lhs.index() < rhs.index()) return false; - return variant::visit_value_at(lhs.index(), greater_equal{}, lhs, rhs); -#else - return rhs.valueless_by_exception() || - (!lhs.valueless_by_exception() && - (lhs.index() > rhs.index() || - (lhs.index() == rhs.index() && - variant::visit_value_at(lhs.index(), greater_equal{}, lhs, rhs)))); -#endif -} - -struct monostate {}; - -inline constexpr bool operator<(monostate, monostate) noexcept { return false; } - -inline constexpr bool operator>(monostate, monostate) noexcept { return false; } - -inline constexpr bool operator<=(monostate, monostate) noexcept { return true; } - -inline constexpr bool operator>=(monostate, monostate) noexcept { return true; } - -inline constexpr bool operator==(monostate, monostate) noexcept { return true; } - -inline constexpr bool operator!=(monostate, monostate) noexcept { - return false; -} - -namespace detail { - -template -inline constexpr bool all_impl(const lib::array &bs, std::size_t idx) { - return idx >= N || (bs[idx] && all_impl(bs, idx + 1)); -} - -template -inline constexpr bool all(const lib::array &bs) { - return all_impl(bs, 0); -} - -} // namespace detail - -template -inline constexpr DECLTYPE_AUTO visit(Visitor &&visitor, Vs &&...vs) - DECLTYPE_AUTO_RETURN( - (detail::all(lib::array{ - {!vs.valueless_by_exception()...}}) - ? (void)0 - : throw_bad_variant_access()), - detail::visitation::variant::visit_value(lib::forward(visitor), - lib::forward(vs)...)) - - template - inline auto swap(variant &lhs, - variant &rhs) noexcept(noexcept(lhs.swap(rhs))) - -> decltype(lhs.swap(rhs)) { - lhs.swap(rhs); -} - -namespace detail { - -template -using enabled_type = T; - -namespace hash { - -template -constexpr bool meets_requirements() noexcept { - return std::is_copy_constructible::value && - std::is_move_constructible::value && - lib::is_invocable_r::value; -} - -template -constexpr bool is_enabled() noexcept { - using H = std::hash; - return meets_requirements() && - std::is_default_constructible::value && - std::is_copy_assignable::value && std::is_move_assignable::value; -} - -} // namespace hash - -} // namespace detail - -#undef AUTO -#undef AUTO_RETURN - -#undef AUTO_REFREF -#undef AUTO_REFREF_RETURN - -#undef DECLTYPE_AUTO -#undef DECLTYPE_AUTO_RETURN - -} // namespace paddlenlp - -namespace std { - -template -struct hash, - paddlenlp::lib::enable_if_t>()...>::value>>> { - using argument_type = paddlenlp::variant; - using result_type = std::size_t; - - inline result_type operator()(const argument_type &v) const { - using paddlenlp::detail::visitation::variant; - std::size_t result = - v.valueless_by_exception() - ? 299792458 // Random value chosen by the universe upon creation - : variant::visit_alt( -#ifdef MPARK_GENERIC_LAMBDAS - [](const auto &alt) { - using alt_type = paddlenlp::lib::decay_t; - using value_type = paddlenlp::lib::remove_const_t< - typename alt_type::value_type>; - return hash{}(alt.value); - } -#else - hasher {} -#endif - , - v); - return hash_combine(result, hash{}(v.index())); - } - - private: -#ifndef MPARK_GENERIC_LAMBDAS - struct hasher { - template - inline std::size_t operator()(const Alt &alt) const { - using alt_type = paddlenlp::lib::decay_t; - using value_type = - paddlenlp::lib::remove_const_t; - return hash{}(alt.value); - } - }; -#endif - - static std::size_t hash_combine(std::size_t lhs, std::size_t rhs) { - return lhs ^= rhs + 0x9e3779b9 + (lhs << 6) + (lhs >> 2); - } -}; - -template <> -struct hash { - using argument_type = paddlenlp::monostate; - using result_type = std::size_t; - - inline result_type operator()(const argument_type &) const noexcept { - return 66740831; // return a fundamentally attractive random value. - } -}; - -} // namespace std - -#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 9 -#pragma GCC diagnostic pop -#endif diff --git a/fast_tokenizer/icu_filters.json b/fast_tokenizer/icu_filters.json deleted file mode 100644 index 42f1735d0421..000000000000 --- a/fast_tokenizer/icu_filters.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "localeFilter": { - "filterType": "language", - "includelist": [ - "en", - "zh" - ] - }, - "featureFilters": { - "coll_tree": "exclude", - "coll_ucadata": "exclude", - - "confusables": "exclude", - - "curr_tree": "exclude", - "curr_supplemental": "exclude", - - "lang_tree": "exclude", - - "unit_tree": "exclude", - - "rbnf_tree": "exclude", - - "zone_tree": "exclude", - "zone_supplemental": "exclude", - - "stringprep": "exclude", - - "translit": "exclude", - - "unames": "exclude", - - "conversion_mappings": "exclude" - } -} diff --git a/fast_tokenizer/perf/README.md b/fast_tokenizer/perf/README.md deleted file mode 100644 index 7d4f4b1d33a9..000000000000 --- a/fast_tokenizer/perf/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# 飞桨FastTokenizer性能测试 - -在PaddleNLP v2.2.0版本中PaddleNLP推出了高性能的Transformer类文本分词器,简称飞桨FastTokenizer。为了验证飞桨FastTokenizer的性能快的特点,PaddleNLP选取了业内常见的一些文本分词器进行了性能对比比较,主要进行性能参考的是HuggingFace BertTokenizer, Tensorflow-text BertTokenizer. 我们以 bert-base-chinese 模型为例进行了文本分词性能实验对比,在中文的数据下进行性能对比实验,下面是具体实验设置信息: -* [HuggingFace Tokenizers(Python)](https://github.com/huggingface/tokenizers): - -```python -from transformers import AutoTokenizer - -hf_tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese", use_fast=False) -``` - -* [HuggingFace Tokenizers(Rust)](https://github.com/huggingface/tokenizers): - -```python -from transformers import AutoTokenizer - -hf_tokenizer_fast = AutoTokenizer.from_pretrained("bert-base-chinese", use_fast=True) -``` - -* [TensorFlow-Text](https://www.tensorflow.org/text/api_docs/python/text/BertTokenizer): - -```python -import tensorflow_text as tf_text - -# vocab 为bert-base-chinese的词汇表 -tf_tokenizer = tf_text.BertTokenizer(vocab) -``` - -* [飞桨FastTokenizer](https://github.com/PaddlePaddle/PaddleNLP/tree/develop/paddlenlp/experimental): - -```python -from paddlenlp.experimental import FastTokenizer - -fast_tokenizer = FastTokenizer.from_pretrained("bert-base-chinese") - -``` - - -## 环境依赖 - -* paddlepaddle >= 2.2.1 -* paddlenlp >= 2.2 -* transformers == 4.11.3 -* tokenizers == 0.10.3 -* tensorflow_text == 2.5.0 - - -```shell -pip install -r requirements.txt -``` - -## 运行 - -```shell -python perf.py -``` - -- 测试环境: - - * CPU: Intel(R) Xeon(R) Gold 6271C CPU @ 2.60GHz,物理核数40 - * GPU: CUDA 10.2, CuDNN 7.6.5, 16G - -- 测试结果: - - -
图片
- -飞桨FastTokenizer与其他框架性能的对比,是在固定文本长度在不同batch size下的分词吞吐量。纵坐标是对数坐标,单位是1w tokens/秒。随着batch size的增大,飞桨FastTokenizer速度会远远超过其他同类产品的实现,尤其是在大batch文本上飞桨框架能充分发挥多核机器的优势,取得领先的速度。 diff --git a/fast_tokenizer/perf/perf.py b/fast_tokenizer/perf/perf.py deleted file mode 100755 index 0b40060b2c71..000000000000 --- a/fast_tokenizer/perf/perf.py +++ /dev/null @@ -1,134 +0,0 @@ -# -*- coding: UTF-8 -*- -# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import argparse -import time - -import tensorflow as tf -import tensorflow_text as tf_text -from transformers import AutoTokenizer - -from paddlenlp.experimental import FastTokenizer, to_tensor -from paddlenlp.transformers import BertTokenizer - -parser = argparse.ArgumentParser() - -# yapf: disable -parser.add_argument("--max_seq_length", default=128, type=int, help="The maximum total input sequence length after tokenization.") -parser.add_argument("--batch_size", default=32, type=int, help="Batch size for tokenization.") -parser.add_argument("--epochs", default=10, type=int, help="Total number of tokenization epochs to perform.") -parser.add_argument("--num_samples", default=100, type=int, help="The number of samples to be tokenized") -# yapf: enable -args = parser.parse_args() - -max_seq_length = args.max_seq_length -batch_size = args.batch_size -epochs = args.epochs -num_samples = args.num_samples -total_tokens = epochs * num_samples * max_seq_length - -text = ( - "在世界几大古代文明中,中华文明源远流长、从未中断,至今仍充满蓬勃生机与旺盛生命力,这在人类历史上是了不起的奇迹。" - "本固根深、一脉相承的历史文化是铸就这一奇迹的重要基础。先秦时期是中华文化的创生期,奠定了此后几千年中华文化发展的" - "基础。考古发现证实,早期中华文明的形成经历了从“满天星斗”到“月明星稀”再到“多元一体”的过程。在这个过程中,不同地域、" - "不同人群的文化交流交融,中华民族最早的大家庭逐渐成形,国家由此诞生,“大同”社会理想和“天下为公,选贤与能,讲信修睦”" - "的价值追求逐渐深入人心。在早期国家形成过程中,我们的先人积累了初步的国家治理经验,包括经济、政治、军事、法律、文化" - "等各个方面,最终以典章、思想的形式进行总结和传承。流传至今的夏商西周国家治理经验、春秋战国诸子百家思想,是先秦时期" - "历史文化的集中反映。秦汉至宋元时期是中华文化的发展期,中华传统文化在这个时期走向成熟并迈向新的高峰。中央集权制度的" - "形成、郡县制度的推广、官僚制度的健全,推动中国传统社会形成国家治理的基本形态,为中国传统社会的长期延续和发展提供了" - "坚实的制度和文化支撑,贯穿其中的价值主线是对“大一统”的坚定追求。与此同时,民为邦本的民本思想、以文化人的文治主张、" - "协和万邦的天下观等,也在实践中得到丰富和完善。在追求“大一统”的历史中,民族精神世代相传,民族英雄史不绝书。" -) - -data = [text[:max_seq_length]] * num_samples - -# BERT Tokenizer using PaddleNLP FastTokenizer -pp_tokenizer = FastTokenizer.from_pretrained("bert-base-chinese") - -batches = [to_tensor(data[idx : idx + batch_size]) for idx in range(0, len(data), batch_size)] - -for batch_data in batches: - input_ids, token_type_ids = pp_tokenizer(text=batch_data, max_seq_len=max_seq_length) - -start = time.time() -for _ in range(epochs): - for batch_data in batches: - input_ids, token_type_ids = pp_tokenizer(batch_data, max_seq_len=max_seq_length) -end = time.time() - -print("The throughput of paddle FastTokenizer: {:,.2f} tokens/s".format((total_tokens / (end - start)))) - -hf_tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese", use_fast=True) - -batches = [data[idx : idx + batch_size] for idx in range(0, len(data), batch_size)] - -for batch_data in batches: - encoded_inputs = hf_tokenizer(batch_data) - -# BERT Tokenizer using HuggingFace AutoTokenizer -start = time.time() -for _ in range(epochs): - for batch_data in batches: - encoded_inputs = hf_tokenizer(batch_data) # , padding=True, truncation=True) -end = time.time() -print("The throughput of huggingface FastTokenizer: {:,.2f} tokens/s".format((total_tokens / (end - start)))) - -# BERT Tokenizer using PaddleNLP BertTokenizer -py_tokenizer = BertTokenizer.from_pretrained("bert-base-chinese") -for batch_data in batches: - encoded_inputs = py_tokenizer(batch_data) - -start = time.time() -for _ in range(epochs): - for batch_data in batches: - encoded_inputs = py_tokenizer(batch_data) -end = time.time() -print("The throughput of paddle BertTokenizer: {:,.2f} tokens/s".format((total_tokens / (end - start)))) - -# BERT Tokenizer using HuggingFace AutoTokenizer -hf_tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese", use_fast=False) - -for batch_data in batches: - encoded_inputs = hf_tokenizer(batch_data) - -start = time.time() -for _ in range(epochs): - for batch_data in batches: - encoded_inputs = hf_tokenizer(batch_data) # , padding=True, truncation=True) -end = time.time() -print("The throughput of huggingface python tokenizer: {:,.2f} tokens/s".format((total_tokens / (end - start)))) - -# BERT Tokenizer using TensorFlow Text -vocab_list = list(py_tokenizer.vocab.token_to_idx.keys()) -lookup_table = tf.lookup.StaticVocabularyTable( - tf.lookup.KeyValueTensorInitializer( - keys=vocab_list, - key_dtype=tf.string, - values=tf.range(tf.size(vocab_list, out_type=tf.int64), dtype=tf.int64), - value_dtype=tf.int64, - ), - num_oov_buckets=1, -) - -tf_tokenizer = tf_text.BertTokenizer(lookup_table) - -for batch_data in batches: - input_ids = tf_tokenizer.tokenize(batch_data) - -start = time.time() -for _ in range(epochs): - for batch_data in batches: - input_ids = tf_tokenizer.tokenize(batch_data) -end = time.time() -print("The throughput of TensorFlow Text BertTokenizer: {:,.2f} tokens/s".format((total_tokens / (end - start)))) diff --git a/fast_tokenizer/perf/requirements.txt b/fast_tokenizer/perf/requirements.txt deleted file mode 100644 index 0a26c4d03ac5..000000000000 --- a/fast_tokenizer/perf/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -paddlenlp>=2.2.0 -transformer==4.11.3 -tensorflow_text==2.5.0 \ No newline at end of file diff --git a/fast_tokenizer/perf/run_all_perf.sh b/fast_tokenizer/perf/run_all_perf.sh deleted file mode 100644 index 052de2a717cd..000000000000 --- a/fast_tokenizer/perf/run_all_perf.sh +++ /dev/null @@ -1,27 +0,0 @@ -# !/bin/sh - -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -for seq_len in 32 64 128 256 512; do -for batch_size in 1 2 4 8 16 32 64; do -mkdir -p seq_len_$seq_len/batch_size_$batch_size -for thread_num in 1 2 4 8 16 32 64; do -echo "Experiment setting: thread_num=$thread_num, batch_size=$batch_size, sequence_length=$seq_len" -export OMP_NUM_THREADS=$thread_num -export RAYON_RS_NUM_CPUS=$thread_num -python perf.py --batch_size $batch_size --max_seq_length $seq_len >seq_len_$seq_len/batch_size_$batch_size/parallel$thread_num.log 2>nohup.out -done -done -done \ No newline at end of file diff --git a/fast_tokenizer/python/CMakeLists.txt b/fast_tokenizer/python/CMakeLists.txt deleted file mode 100644 index ab0fe0065e3b..000000000000 --- a/fast_tokenizer/python/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -# 1. Copy the python tokenizers directory to the binary directory -add_custom_target(copy_python_tokenizers ALL - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/python ${CMAKE_BINARY_DIR}/python - DEPENDS core_tokenizers) - - -# 2. Copy setup.py -add_custom_target(copy_setup ALL - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/setup.py ${CMAKE_BINARY_DIR}/setup.py) - -# 3. Copy the core_tokenizers.so to python tokenizers directory -set(TOKENIZER_CORE_NAME "core_tokenizers") -set(TOKENIZER_DST_DIR ${CMAKE_BINARY_DIR}/python/fast_tokenizer) -set(TOKENIZER_SRC_DIR ${CMAKE_BINARY_DIR}/fast_tokenizer) -set(TOKENIZER_THIRD_PARTY_DIR ${CMAKE_BINARY_DIR}/third_party) - -IF(WIN32) -set(ICU_DLL_DIR ${TOKENIZER_THIRD_PARTY_DIR}/icu/src/extern_icu/icu4c/bin64) -add_custom_target(copy_shared_library ALL - COMMAND ${CMAKE_COMMAND} -E copy ${TOKENIZER_SRC_DIR}/${TOKENIZER_CORE_NAME}.pyd ${TOKENIZER_SRC_DIR}/${TOKENIZER_CORE_NAME}.lib ${TOKENIZER_DST_DIR} - COMMAND ${CMAKE_COMMAND} -E copy ${ICU_DLL_DIR}/icudt70.dll ${ICU_DLL_DIR}/icuuc70.dll ${TOKENIZER_DST_DIR}/libs - DEPENDS copy_python_tokenizers) -ELSE(WIN32) -add_custom_target(copy_shared_library ALL - COMMAND ${CMAKE_COMMAND} -E copy ${TOKENIZER_SRC_DIR}/${TOKENIZER_CORE_NAME}.so ${TOKENIZER_DST_DIR} - DEPENDS copy_python_tokenizers) -ENDIF() - -add_custom_target(create_commit_id_file ALL - COMMAND ${GIT_EXECUTABLE} log -1 --format=%H > ${TOKENIZER_DST_DIR}/commit.log - DEPENDS copy_python_tokenizers) diff --git a/fast_tokenizer/python/fast_tokenizer/__init__.py b/fast_tokenizer/python/fast_tokenizer/__init__.py deleted file mode 100644 index ea7b5a9570f4..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/__init__.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__version__ = "1.0.2" - -import os -import platform -import sys -from typing import Dict, List, Tuple, Union - -try: - current_path = os.path.abspath(os.path.dirname(__file__)) - if os.name == "nt": - third_lib_path = current_path + os.sep + "libs" - # Will load shared library from 'path' on windows - os.environ["path"] = current_path + ";" + third_lib_path + ";" + os.environ["path"] - sys.path.insert(0, third_lib_path) - # Note: from python3.8, PATH will not take effect - # https://github.com/python/cpython/pull/12302 - # Use add_dll_directory to specify dll resolution path - if sys.version_info[:2] >= (3, 8): - os.add_dll_directory(third_lib_path) -except ImportError as e: - if os.name == "nt": - executable_path = os.path.abspath(os.path.dirname(sys.executable)) - raise ImportError( - """NOTE: You may need to run \"set PATH=%s;%%PATH%%\" - if you encounters \"DLL load failed\" errors. If you have python - installed in other directory, replace \"%s\" with your own - directory. The original error is: \n %s""" - % (executable_path, executable_path, str(e)) - ) - else: - raise ImportError( - """NOTE: You may need to run \"export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH\" - if you encounters \"libmkldnn.so not found\" errors. If you have python - installed in other directory, replace \"/usr/local/lib\" with your own - directory. The original error is: \n""" - + str(e) - ) -except Exception as e: - raise e - -from . import core_tokenizers as C -from .c_wrap import * - -from . import decoders, models, normalizers, postprocessors, pretokenizers -from .tokenizers_impl import ( - ClipFastTokenizer, - ErnieFastTokenizer, - SentencePieceBPEFastTokenizer, -) diff --git a/fast_tokenizer/python/fast_tokenizer/c_wrap.py b/fast_tokenizer/python/fast_tokenizer/c_wrap.py deleted file mode 100644 index 9645afceab8a..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/c_wrap.py +++ /dev/null @@ -1,505 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Dict, List, Tuple, Union - -from . import core_tokenizers as C - -TextInputSequence = str -PreTokenizedInputSequence = Union[List[str], Tuple[str]] - -TextEncodeInput = Union[ - TextInputSequence, - Tuple[TextInputSequence, TextInputSequence], - List[TextInputSequence], -] - -PreTokenizedEncodeInput = Union[ - PreTokenizedInputSequence, - Tuple[PreTokenizedInputSequence, PreTokenizedInputSequence], - List[PreTokenizedInputSequence], -] - -InputSequence = Union[TextInputSequence, PreTokenizedInputSequence] - -EncodeInput = Union[TextEncodeInput, PreTokenizedEncodeInput] - - -class OffsetType: - CHAR = C.OffsetType.CHAR - BYTE = C.OffsetType.BYTE - - -class Direction: - LEFT = C.Direction.LEFT - RIGHT = C.Direction.RIGHT - - -class TruncStrategy: - LONGEST_FIRST = C.TruncStrategy.LONGEST_FIRST - ONLY_FIRST = C.TruncStrategy.ONLY_FIRST - ONLY_SECOND = C.TruncStrategy.ONLY_SECOND - - -class PadStrategy: - BATCH_LONGEST = C.PadStrategy.BATCH_LONGEST - FIXED_SIZE = C.PadStrategy.FIXED_SIZE - - -class SplitMode: - REMOVED = C.SplitMode.REMOVED - ISOLATED = C.SplitMode.ISOLATED - MERGED_WITH_NEXT = C.SplitMode.MERGED_WITH_NEXT - MERGED_WITH_PREVIOUS = C.SplitMode.MERGED_WITH_PREVIOUS - CONTIGUOUS = C.SplitMode.CONTIGUOUS - - -class Token: - def __init__(self): - self._token = C.Token() - - @property - def id(self): - return self._token.id - - @id.setter - def id(self, id: int): - self._token.id = id - - @property - def value(self): - return self._token.value - - @value.setter - def value(self, value: str): - self._token.value = value - - @property - def offset(self): - return self._token.offset - - @offset.setter - def offset(self, offset: Tuple[int, int]): - self._token.offset = offset - - def __repr__(self): - return self._token.__repr__() - - -class PadMethod: - def __init__(self): - self._pad_method = C.PadMethod() - - @property - def strategy(self): - return self._pad_method.strategy - - @strategy.setter - def strategy(self, strategy: str): - """Set the strategy of PadMethod. - :param strategy: (str) The strategy of PadMethod, 'batch_longest' and 'fixed_size' are valid - :return None - """ - self._pad_method.strategy = getattr(PadStrategy, strategy.upper()) - - @property - def direction(self): - return self._pad_method.direction - - @direction.setter - def direction(self, direction: str): - """Set the direction of PadMethod. - :param strategy: (str) The direction of PadMethod, 'left' and 'right' are valid - :return None - """ - self._pad_method.direction = getattr(Direction, direction.upper()) - - @property - def pad_id(self): - return self._pad_method.pad_id - - @pad_id.setter - def pad_id(self, pad_id: int): - self._pad_method.pad_id = pad_id - - @property - def pad_token_type_id(self): - return self._pad_method.pad_token_type_id - - @pad_token_type_id.setter - def pad_token_type_id(self, pad_token_type_id: int): - self._pad_method.pad_token_type_id = pad_token_type_id - - @property - def pad_token(self): - return self._pad_method.pad_token - - @pad_token.setter - def pad_token(self, pad_token: str): - self._pad_method.pad_token = pad_token - - @property - def pad_len(self): - return self._pad_method.pad_len - - @pad_len.setter - def pad_len(self, pad_len: int): - self._pad_method.pad_len = pad_len - - @property - def pad_to_multiple_of(self): - return self._pad_method.pad_to_multiple_of - - @pad_to_multiple_of.setter - def pad_to_multiple_of(self, pad_to_multiple_of): - self._pad_method.pad_to_multiple_of = pad_to_multiple_of - - -class TruncMethod: - def __init__(self): - self._trunc_method = C.TruncMethod() - - @property - def max_len(self): - return self._trunc_method.max_len - - @max_len.setter - def max_len(self, max_len: int): - self._trunc_method.max_len = max_len - - @property - def strategy(self): - return self._trunc_method.strategy - - @strategy.setter - def strategy(self, strategy: str): - """Set the strategy of TruncMethod. - :param strategy: (str) The strategy of PadMethod, 'longest_first', 'only_first' and 'only_second' are valid - :return None - """ - self._trunc_method.strategy = getattr(TruncStrategy, strategy.upper()) - - @property - def direction(self): - return self._trunc_method.direction - - @direction.setter - def direction(self, direction: str): - """Set the direction of TruncMethod. - :param strategy: (str) The direction of TruncMethod, 'left' and 'right' are valid - :return None - """ - self._trunc_method.direction = getattr(Direction, direction.upper()) - - @property - def stride(self): - return self._trunc_method.stride - - @stride.setter - def stride(self, stride: int): - self._trunc_method.stride = stride - - -class AddedToken: - def __init__(self, content="", single_word=False, lstrip=False, rstrip=False, normalized=True): - self._added_token = C.AddedToken(content, single_word, lstrip, rstrip, normalized) - - @property - def content(self): - return self._added_token.content - - @property - def get_is_special(self): - return self._added_token.get_is_special - - @property - def normalized(self): - return self._added_token.normalized - - @property - def lstrip(self): - return self._added_token.lstrip - - @property - def rstrip(self): - return self._added_token.rstrip - - @property - def single_word(self): - return self._added_token.single_word - - def __eq__(self, other): - return self._added_token == other._added_token - - -class Encoding: - def __init__( - self, - ids: List[int], - type_ids: List[int], - tokens: List[str], - words_idx: List[int], - offsets: List[Tuple[int, int]], - special_tokens_mask: List[int], - attention_mask: List[int], - overflowing: List, - sequence_ranges: Dict[str, Tuple[int, int]], - ): - self._encoding = C.Encoding( - ids, - type_ids, - tokens, - words_idx, - offsets, - special_tokens_mask, - attention_mask, - overflowing, - sequence_ranges, - ) - - def __str__(self): - return str(self._encoding) - - def __repr__(self): - return self._encoding.__repr__() - - def __len__(self): - return len(self._encoding) - - @property - def n_sequences(self): - return self._encoding.n_sequences - - @property - def tokens(self): - return self._encoding.tokens - - @property - def word_ids(self): - return self._encoding.word_ids - - @property - def sequence_ids(self): - return self._encoding.sequence_ids - - @property - def ids(self): - return self._encoding.ids - - @property - def type_ids(self): - return self._encoding.type_ids - - @property - def offsets(self): - return self._encoding.offsets - - @property - def special_tokens_mask(self): - return self._encoding.special_tokens_mask - - @property - def attention_mask(self): - return self._encoding.attention_mask - - @property - def overflowing(self): - return self._encoding.overflowing - - def set_sequence_ids(self, sequence_id: int): - return self._encoding.set_sequence_ids(sequence_id) - - def char_to_token(self, char_pos, sequence_index: int = 0): - return self._encoding.char_to_token(char_pos, sequence_index) - - @staticmethod - def merge(encodings: List, growing_offsets: bool = True): - return C.Encoding.merge(encodings, growing_offsets) - - def token_to_chars(self, token_index: int): - return self._encoding.token_to_chars(token_index) - - def token_to_sequence(self, token_index: int): - return self._encoding.token_to_sequence(token_index) - - def token_to_word(self, token_index: int): - return self._encoding.token_to_word(token_index) - - def word_to_chars(self, word_index: int, sequence_index: int = 0): - return self._encoding.word_to_chars(word_index, sequence_index) - - def word_to_tokens(self, word_index: int, sequence_index: int = 0): - return self._encoding.word_to_tokens(word_index, sequence_index) - - def truncate(self, max_length: int, stride: int = 0, direction: str = "right"): - return self._encoding.truncate(max_length, stride, direction) - - def pad( - self, length: int, direction: str = "right", pad_id: int = 0, pad_type_id: int = 0, pad_token: str = "[PAD]" - ): - return self._encoding.pad(length, direction, pad_id, pad_type_id, pad_token) - - -class Tokenizer: - def __init__(self, model): - self._tokenizer = None - if model is not None: - self._tokenizer = C.Tokenizer(model._model) - - @property - def normalizer(self): - return self._tokenizer.normalizer - - @normalizer.setter - def normalizer(self, normalizer): - self._tokenizer.normalizer = normalizer._normalizer - - @property - def pretokenizer(self): - return self._tokenizer.pretokenizer - - @pretokenizer.setter - def pretokenizer(self, pretokenizer): - self._tokenizer.pretokenizer = pretokenizer._pretokenizer - - @property - def model(self): - return self._tokenizer.model - - @model.setter - def model(self, model): - self._tokenizer.model = model._model - - @property - def postprocessor(self): - return self._tokenizer.postprocessor - - @postprocessor.setter - def postprocessor(self, postprocessor): - self._tokenizer.postprocessor = postprocessor._postprocessor - - @property - def decoder(self): - return self._tokenizer.decoder - - @decoder.setter - def decoder(self, decoder): - self._tokenizer.decoder = decoder._decoder - - @property - def padding(self): - return self._tokenizer.padding - - @property - def truncation(self): - return self._tokenizer.truncation - - def add_special_tokens(self, tokens: List[str]): - return self._tokenizer.add_special_tokens(tokens) - - def add_tokens(self, tokens: List[str]): - return self._tokenizer.add_tokens(tokens) - - def enable_padding( - self, - direction: str = "right", - pad_id: int = 0, - pad_type_id: int = 0, - pad_token: str = "[PAD]", - length: int = None, - pad_to_multiple_of: int = None, - ): - return self._tokenizer.enable_padding(direction, pad_id, pad_type_id, pad_token, length, pad_to_multiple_of) - - def disable_padding(self): - return self._tokenizer.disable_padding() - - def enable_truncation( - self, max_length: int, stride: int = 0, strategy: str = "longest_first", direction: str = "right" - ): - return self._tokenizer.enable_truncation(max_length, stride, strategy, direction) - - def disable_truncation(self): - return self._tokenizer.disable_truncation() - - def get_vocab(self, with_added_vocabulary: bool = True): - return self._tokenizer.get_vocab(with_added_vocabulary) - - def get_vocab_size(self, with_added_vocabulary: bool = True): - return self._tokenizer.get_vocab_size(with_added_vocabulary) - - def encode( - self, - sequence: InputSequence, - pair: InputSequence = None, - is_pretokenized: bool = False, - add_special_tokens: bool = True, - ): - return self._tokenizer.encode(sequence, pair, is_pretokenized, add_special_tokens) - - def encode_batch( - self, - input: Union[List[EncodeInput], Tuple[EncodeInput]], - add_special_tokens: bool = True, - is_pretokenized: bool = False, - ): - return self._tokenizer.encode_batch(input, add_special_tokens, is_pretokenized) - - def decode(self, ids: List[int], skip_special_tokens: bool = True): - return self._tokenizer.decode(ids, skip_special_tokens) - - def decode_batch(self, sequences: List[List[int]], skip_special_tokens: bool = True): - return self._tokenizer.decode_batch(sequences, skip_special_tokens) - - def id_to_token(self, id: int): - return self._tokenizer.id_to_token(id) - - def token_to_id(self, token: str): - return self._tokenizer.token_to_id(token) - - def num_special_tokens_to_add(self, is_pair: bool = True): - return self._tokenizer.num_special_tokens_to_add(is_pair) - - def save(self, path: str, pretty: bool = True): - return self._tokenizer.save(path, pretty) - - def to_str(self, pretty: bool = True): - return self._tokenizer.to_str(pretty) - - @staticmethod - def from_str(json: str): - tr = Tokenizer(None) - tr._tokenizer = C.Tokenizer.from_str(json) - return tr - - @staticmethod - def from_file(json: str): - tr = Tokenizer(None) - tr._tokenizer = C.Tokenizer.from_file(json) - return tr - - -def set_thread_num(thread_num): - """Set the number of threads for accelerating batch tokenization - :param thread_num: (int) The number of threads - :return None - """ - C.set_thread_num(thread_num) - - -def get_thread_num(): - """Get the number of tokenization threads - :return int - """ - return C.get_thread_num() diff --git a/fast_tokenizer/python/fast_tokenizer/decoders/__init__.py b/fast_tokenizer/python/fast_tokenizer/decoders/__init__.py deleted file mode 100644 index 142e5af3345a..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/decoders/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC -from typing import List - -from .. import C - - -class Decoder(ABC): - def decode(self, tokens: List[str]): - return self._decoder.decode(tokens) - - -class WordPiece(Decoder): - def __init__(self, prefix: str = "##", cleanup: bool = True): - self._decoder = C.decoders.WordPiece(prefix, cleanup) diff --git a/fast_tokenizer/python/fast_tokenizer/libs/__init__.py b/fast_tokenizer/python/fast_tokenizer/libs/__init__.py deleted file mode 100644 index ac566ed8eb61..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/libs/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# used for setup.py.in to store the thirdparty shared libraries diff --git a/fast_tokenizer/python/fast_tokenizer/models/__init__.py b/fast_tokenizer/python/fast_tokenizer/models/__init__.py deleted file mode 100644 index 567944e4d2b7..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/models/__init__.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Tuple, Union, List, Dict -from abc import ABC - -from .. import C - - -class Model(ABC): - def tokenize(self, tokens: List[str]): - return self._model.tokenize(tokens) - - def token_to_id(self, token: str): - return self._model.token_to_id(token) - - def id_to_token(self, id: int): - return self._model.id_to_token(id) - - def get_vocab(self): - return self._model.get_vocab() - - def get_vocab_size(self): - return self._model.get_vocab_size() - - def save(self, folder: str, prefix: str = None): - return self._model.save(folder, prefix) - - -class WordPiece(Model): - def __init__( - self, - vocab: Dict[str, int], - unk_token: str = "[UNK]", - max_input_chars_per_word: int = 100, - continuing_subword_prefix: str = "##", - handle_chinese_chars: bool = True, - ): - self._model = None - if vocab is not None: - self._model = C.models.WordPiece( - vocab, unk_token, max_input_chars_per_word, continuing_subword_prefix, handle_chinese_chars - ) - - @staticmethod - def read_file(vocab: str): - """Read a vocab.txt file - - :params vocab: (str) The path to a vocab.txt file - :return: Dict[str, int], The vocabulary as a dict - """ - return C.models.WordPiece.read_file(vocab) - - @staticmethod - def from_file( - vocab: str, - unk_token: str = "[UNK]", - max_input_chars_per_word: int = 100, - continuing_subword_prefix: str = "continuing_subword_prefix", - ): - """Load a WordPiece instance from vocab file. - - :param vocab: (str) The path to a vocab.txt file - :param unk_token: (str) The unknown token - :param max_input_chars_per_word: (int) The max number of char when tokenize a word - :param continuing_subword_prefix: (str) The latter subword prefix. - :return: An instance of WordPiece. - """ - wp = WordPiece(None) - wp._model = C.models.WordPiece.from_file(vocab, unk_token, max_input_chars_per_word, continuing_subword_prefix) - return wp - - -class FastWordPiece(Model): - def __init__( - self, - vocab: Dict[str, int], - unk_token: str = "[UNK]", - max_input_chars_per_word: int = 100, - continuing_subword_prefix: str = "##", - with_pretokenization: bool = False, - ): - self._model = None - if vocab is not None: - self._model = C.models.FastWordPiece( - vocab, unk_token, max_input_chars_per_word, continuing_subword_prefix, with_pretokenization - ) - - @staticmethod - def read_file(vocab: str): - """Read a vocab.txt file - - :params vocab: (str) The path to a vocab.txt file - :return: Dict[str, int], The vocabulary as a dict - """ - return C.models.FastWordPiece.read_file(vocab) - - @staticmethod - def from_file( - vocab: str, - unk_token: str = "[UNK]", - max_input_chars_per_word: int = 100, - continuing_subword_prefix: str = "continuing_subword_prefix", - with_pretokenization: bool = False, - ): - """Load a FastWordPiece instance from vocab file. - - :param vocab: (str) The path to a vocab.txt file - :param unk_token: (str) The unknown token - :param max_input_chars_per_word: (int) The max number of char when tokenize a word - :param continuing_subword_prefix: (str) The latter subword prefix. - :param with_pretokenization: (bool) Whether to pretokenize sequence during the wordpiece tokenization. - If it's true, the end to end tokenization would be faster. - :return: An instance of FastWordPiece. - """ - wp = FastWordPiece(None) - wp._model = C.models.FastWordPiece.from_file( - vocab, unk_token, max_input_chars_per_word, continuing_subword_prefix, with_pretokenization - ) - return wp - - -class BPE(Model): - def __init__( - self, - vocab: Dict[str, int] = None, - merges: List[Tuple[str, str]] = None, - cache_capacity: int = None, - dropout: float = None, - unk_token: str = None, - continuing_subword_prefix: str = None, - end_of_word_suffix: str = None, - fuse_unk: bool = None, - ): - self._model = C.models.BPE( - vocab, merges, cache_capacity, dropout, unk_token, continuing_subword_prefix, end_of_word_suffix, fuse_unk - ) - - @staticmethod - def read_file(vocab: str, merges: str): - return C.models.BPE.read_file(vocab, merges) - - @staticmethod - def from_file(vocab: str, merges: str, **kwargs): - bpe = BPE() - bpe._model = C.models.BPE.from_file(vocab, merges, **kwargs) - return bpe - - -class Unigram(Model): - def __init__(self, vocab: List[Tuple[str, float]] = None, unk_id: int = None): - self._model = C.models.Unigram(vocab, unk_id) - - def set_filter_token(self, filter_token: str = ""): - return self._model.set_filter_token(filter_token) - - def set_split_rule(self, split_rule: str = ""): - return self._model.set_split_rule(split_rule) diff --git a/fast_tokenizer/python/fast_tokenizer/normalizers/__init__.py b/fast_tokenizer/python/fast_tokenizer/normalizers/__init__.py deleted file mode 100644 index 6e8fa6e45b2d..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/normalizers/__init__.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC - -from .. import C - - -class NormalizedString: - def __init__(self, raw_str: str): - self._normalized = C.normalizers.NormalizedString(raw_str) - - def __str__(self): - return str(self._normalized) - - -class Normalizer(ABC): - def normalize_str(self, sequence: str): - return self._normalizer.normalize_str(sequence) - - def __call__(self, normalized: NormalizedString): - return self._normalizer(normalized._normalized) - - def __getstate__(self): - return self._normalizer.__getstate__() - - -class BertNormalizer(Normalizer): - def __init__( - self, - clean_text: bool = True, - handle_chinese_chars: bool = True, - strip_accents: bool = True, - lowercase: bool = True, - ): - self._normalizer = C.normalizers.BertNormalizer(clean_text, handle_chinese_chars, strip_accents, lowercase) - - -class ReplaceNormalizer(Normalizer): - def __init__(self, pattern: str, content: str): - self._normalizer = C.normalizers.ReplaceNormalizer(pattern, content) - - -class StripNormalizer(Normalizer): - def __init__(self, left: bool = True, right: bool = True): - self._normalizer = C.normalizers.StripNormalizer(left, right) - - -class StripAccentsNormalizer(Normalizer): - def __init__(self): - self._normalizer = C.normalizers.StripAccentsNormalizer() - - -class NFCNormalizer(Normalizer): - def __init__(self): - self._normalizer = C.normalizers.NFCNormalizer() - - -class NFDNormalizer(Normalizer): - def __init__(self): - self._normalizer = C.normalizers.NFDNormalizer() - - -class NFKCNormalizer(Normalizer): - def __init__(self): - self._normalizer = C.normalizers.NFKCNormalizer() - - -class NFKDNormalizer(Normalizer): - def __init__(self): - self._normalizer = C.normalizers.NFKDNormalizer() - - -class NmtNormalizer(Normalizer): - def __init__(self): - self._normalizer = C.normalizers.NmtNormalizer() - - -class LowercaseNormalizer(Normalizer): - def __init__(self): - self._normalizer = C.normalizers.LowercaseNormalizer() - - -class SequenceNormalizer(Normalizer): - def __init__(self, normalizer_list=[]): - normalizer_list = [normalizer._normalizer for normalizer in normalizer_list] - self._normalizer = C.normalizers.SequenceNormalizer(normalizer_list) - - -class PrecompiledNormalizer(Normalizer): - def __init__(self, precompiled_charsmap: str): - self._normalizer = C.normalizers.PrecompiledNormalizer(precompiled_charsmap) diff --git a/fast_tokenizer/python/fast_tokenizer/postprocessors/__init__.py b/fast_tokenizer/python/fast_tokenizer/postprocessors/__init__.py deleted file mode 100644 index 496c6413b76b..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/postprocessors/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC -from typing import List, Tuple, Union - -from .. import C, Encoding - - -class PostProcessor(ABC): - def num_special_tokens_to_add(self, is_pair: bool = True): - return self._postprocessor.num_special_tokens_to_add(is_pair) - - def __call__(self, encoding: Encoding, pair_encoding: Encoding, add_special_tokens: bool): - return self._postprocessor(encoding, pair_encoding, add_special_tokens) - - -class BertPostProcessor(PostProcessor): - def __init__(self, sep: Tuple[str, int] = ("[SEP]", 102), cls: Tuple[str, int] = ("[CLS]", 101)): - self._postprocessor = C.postprocessors.BertPostProcessor(sep, cls) - - -class RobertaPostProcessor(PostProcessor): - def __init__( - self, - sep: Tuple[str, int] = ("", 2), - cls: Tuple[str, int] = ("", 0), - trim_offsets: bool = True, - add_prefix_space: bool = True, - ): - self._postprocessor = C.postprocessors.RobertaPostProcessor(sep, cls, trim_offsets, add_prefix_space) - - -class ByteLevelPostProcessor(PostProcessor): - def __init__(self, add_prefix_space: bool = True, trim_offsets: bool = True, use_regex: bool = True): - self._postprocessor = C.postprocessors.ByteLevelPostProcessor(add_prefix_space, trim_offsets, use_regex) - - -class TemplatePostProcessor(PostProcessor): - def __init__( - self, single: Union[str, List[str]], pair: Union[str, List[str]], special_tokens: List[Tuple[str, int]] - ): - self._postprocessor = C.postprocessors.TemplatePostProcessor(single, pair, special_tokens) diff --git a/fast_tokenizer/python/fast_tokenizer/pretokenizers/__init__.py b/fast_tokenizer/python/fast_tokenizer/pretokenizers/__init__.py deleted file mode 100644 index 6458f579ea1e..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/pretokenizers/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import ABC -from typing import Dict, List, Tuple, Union - -from .. import C, OffsetType, Token -from ..normalizers import NormalizedString - - -class StringSplit: - def __init__(self, nomalized_text: NormalizedString, tokens: List[Token]): - tokens = [token._token for token in tokens] - self._string_split = C.pretokenizers.StringSplit(nomalized_text._normalized, tokens) - - @property - def normalized(self): - return NormalizedString(self._string_split.normalized) - - @normalized.setter - def normalized(self, normalized: NormalizedString): - self._string_split.normalized = normalized._normalized - - @property - def tokens(self): - return self._string_split.tokens - - @tokens.setter - def tokens(self, tokens: List[Token]): - self._string_split.tokens = [token._token for token in tokens] - - -class PreTokenizedString: - def __init__(self, text: str): - self._pretokenized = C.pretokenizers.PreTokenizedString(text) - - def get_string_split(self, idx: int): - return self._pretokenized.get_string_split(idx) - - def get_string_splits_size(self): - return self._pretokenized.get_string_splits_size() - - def get_original_text(self): - return self._pretokenized.get_original_text() - - def get_splits(self, offset_referential: str = "original", offset_type: str = "char"): - """ - param offset_referential: "original" or "normalized" - param offset_type: "char" or "byte" - """ - return self._pretokenized.get_splits(offset_referential, offset_type) - - def to_encoding(self, word_idx: List[int], type_id: int, offset_type): - return self._pretokenized.to_encoding(word_idx, type_id, offset_type) - - -class PreTokenizer(ABC): - def __call__(self, pretokenized: PreTokenizedString): - return self._pretokenizer(pretokenized._pretokenized) - - def pretokenize_str(self, pretokenized_str: str): - pretokenized = PreTokenizedString(pretokenized_str) - self(pretokenized) - splits = pretokenized.get_splits() - result = [(s, offset) for s, offset, tokens in splits] - return result - - -class WhitespacePreTokenizer(PreTokenizer): - def __init__(self): - self._pretokenizer = C.pretokenizers.WhitespacePreTokenizer() - - -class WhitespaceAndPunctuationPreTokenizer(PreTokenizer): - def __init__(self): - self._pretokenizer = C.pretokenizers.WhitespaceAndPunctuationPreTokenizer() - - -class BertPreTokenizer(PreTokenizer): - def __init__(self): - self._pretokenizer = C.pretokenizers.BertPreTokenizer() - - -class MetaSpacePreTokenizer(PreTokenizer): - def __init__(self, replacement: str = "_", add_prefix_space: bool = True): - self._pretokenizer = C.pretokenizers.MetaSpacePreTokenizer(replacement, add_prefix_space) - - -class SequencePreTokenizer(PreTokenizer): - def __init__(self, pretokenizers: List): - pretokenizers = [pretokenizer._pretokenizer for pretokenizer in pretokenizers] - self._pretokenizer = C.pretokenizers.SequencePreTokenizer(pretokenizers) - - -class ByteLevelPreTokenizer(PreTokenizer): - def __init__(self, add_prefix_space: bool = True, use_regex: bool = True): - self._pretokenizer = C.pretokenizers.ByteLevelPreTokenizer(add_prefix_space, use_regex) - - -class SplitPreTokenizer(PreTokenizer): - def __init__(self, pattern: str, split_mode: int, invert: bool = True): - self._pretokenizer = C.pretokenizers.SplitPreTokenizer(pattern, C.SplitMode(split_mode), invert) diff --git a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/__init__.py b/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/__init__.py deleted file mode 100644 index cfb9bfb4859b..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from .base_tokenizer import BaseFastTokenizer -from .ernie import ErnieFastTokenizer -from .sentencepiece_bpe import SentencePieceBPEFastTokenizer -from .clip import ClipFastTokenizer diff --git a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/base_tokenizer.py b/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/base_tokenizer.py deleted file mode 100644 index c6c5631d53b2..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/base_tokenizer.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from fast_tokenizer import Tokenizer - -__all__ = ["BaseFastTokenizer"] - - -class BaseFastTokenizer: - def __init__(self, tokenizer_impl, parma_dict=None): - self._tokenizer = tokenizer_impl - self._parma_dict = parma_dict if parma_dict is not None else {} - - def __repr__(self): - return "Tokenizer(vocabulary_size={}, {})".format( - self._tokenizer.get_vocab_size(), - ", ".join(k + "=" + str(v) for k, v in self._parma_dict.items()), - ) - - def num_special_tokens_to_add(self, is_pair): - return self._tokenizer.num_special_tokens_to_add(is_pair) - - def get_vocab(self, with_added_tokens=True): - return self._tokenizer.get_vocab(with_added_tokens=with_added_tokens) - - def get_vocab_size(self, with_added_tokens=True): - return self._tokenizer.get_vocab_size(with_added_tokens=with_added_tokens) - - def enable_padding( - self, - direction="right", - pad_id=0, - pad_type_id=0, - pad_token="[PAD]", - pad_to_multiple_of=None, - length=None, - ): - return self._tokenizer.enable_padding( - direction=direction, - pad_to_multiple_of=pad_to_multiple_of, - pad_id=pad_id, - pad_type_id=pad_type_id, - pad_token=pad_token, - length=length, - ) - - def disable_padding(self): - self._tokenizer.disable_padding() - - @property - def padding(self): - return self._tokenizer.padding - - def enable_truncation(self, max_length, stride=0, strategy="longest_first", direction="right"): - self._tokenizer.enable_truncation(max_length, stride, strategy, direction) - - def disable_truncation(self): - self._tokenizer.disable_truncation() - - def truncation(self): - return self._tokenizer.truncation - - def add_tokens(self, tokens): - return self._tokenizer.add_tokens(tokens) - - def add_special_tokens(self, special_tokens): - return self._tokenizer.add_special_tokens(special_tokens) - - def encode( - self, - sequence, - pair=None, - is_pretokenized=False, - add_special_tokens=True, - ): - if sequence is None: - raise ValueError("encode: `sequence` can't be `None`") - return self._tokenizer.encode(sequence, pair, is_pretokenized, add_special_tokens) - - def encode_batch(self, inputs, add_special_tokens=True, is_pretokenized=False): - if inputs is None: - raise ValueError("encode_batch: `inputs` can't be `None`") - return self._tokenizer.encode_batch(inputs, add_special_tokens, is_pretokenized) - - def decode(self, ids, skip_special_tokens=True) -> str: - if ids is None: - raise ValueError("None input is not valid. Should be a list of integers.") - - return self._tokenizer.decode(ids, skip_special_tokens=skip_special_tokens) - - def decode_batch(self, sequences, skip_special_tokens=True) -> str: - if sequences is None: - raise ValueError("None input is not valid. Should be list of list of integers.") - - return self._tokenizer.decode_batch(sequences, skip_special_tokens=skip_special_tokens) - - def token_to_id(self, token): - return self._tokenizer.token_to_id(token) - - def id_to_token(self, id): - return self._tokenizer.id_to_token(id) - - def post_process(self, encoding, pair=None, add_special_tokens=True): - return self._tokenizer.post_process(encoding, pair, add_special_tokens) - - @property - def model(self): - return self._tokenizer.model - - @model.setter - def model(self, model): - self._tokenizer.model = model - - @property - def normalizer(self): - return self._tokenizer.normalizer - - @normalizer.setter - def normalizer(self, normalizer): - self._tokenizer.normalizer = normalizer - - @property - def pretokenizer(self): - return self._tokenizer.pretokenizer - - @pretokenizer.setter - def pretokenizer(self, pretokenizer): - self._tokenizer.pretokenizer = pretokenizer - - @property - def postprocessor(self): - return self._tokenizer.postprocessor - - @postprocessor.setter - def postprocessor(self, postprocessor): - self._tokenizer.postprocessor = postprocessor - - @property - def decoder(self): - return self._tokenizer.decoder - - @decoder.setter - def decoder(self, decoder): - self._tokenizer.decoder = decoder - - def save(self, path, pretty=True): - self._tokenizer.save(path, pretty) - - def to_str(self, pretty=True): - return self._tokenizer.to_str(pretty) - - @staticmethod - def from_str(json): - return Tokenizer.from_str(json) - - @staticmethod - def from_file(path): - return Tokenizer.from_file(path) diff --git a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/clip.py b/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/clip.py deleted file mode 100644 index 0add6051bc44..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/clip.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from .base_tokenizer import BaseFastTokenizer - -from fast_tokenizer.normalizers import NFCNormalizer, ReplaceNormalizer, LowercaseNormalizer, SequenceNormalizer -from fast_tokenizer.pretokenizers import SplitPreTokenizer, ByteLevelPreTokenizer, SequencePreTokenizer -from fast_tokenizer.models import BPE -from fast_tokenizer.postprocessors import RobertaPostProcessor -from fast_tokenizer import Tokenizer, SplitMode - -__all__ = ["ClipFastTokenizer"] - - -class ClipFastTokenizer(BaseFastTokenizer): - def __init__( - self, - vocab=None, - merges=None, - max_length=None, - unk_token="<|endoftext|>", - pad_token="<|endoftext|>", - bos_token="<|startoftext|>", - eos_token="<|endoftext|>", - add_prefix_space=False, - continuing_subword_prefix="", - end_of_word_suffix="", - trim_offsets=False, - ): - # Init Tokenizer instance using tokenization model - tokenizer = Tokenizer( - BPE( - vocab, - merges, - unk_token=unk_token, - continuing_subword_prefix=continuing_subword_prefix, - end_of_word_suffix=end_of_word_suffix, - fuse_unk=False, - ) - ) - - # Add special tokens - bos_token_id = 0 - eos_token_id = 1 - if tokenizer.token_to_id(str(unk_token)) is not None: - tokenizer.add_special_tokens([str(unk_token)]) - if tokenizer.token_to_id(str(pad_token)) is not None: - tokenizer.add_special_tokens([str(pad_token)]) - if tokenizer.token_to_id(str(bos_token)) is not None: - bos_token_id = tokenizer.token_to_id(str(bos_token)) - tokenizer.add_special_tokens([str(bos_token)]) - if tokenizer.token_to_id(str(eos_token)) is not None: - eos_token_id = tokenizer.token_to_id(str(eos_token)) - tokenizer.add_special_tokens([str(eos_token)]) - - # Set the normalizer - tokenizer.normalizer = SequenceNormalizer( - [NFCNormalizer(), ReplaceNormalizer(r"\s+", " "), LowercaseNormalizer()] - ) - - # Set the pretokenizer - tokenizer.pretokenizer = SequencePreTokenizer( - [ - SplitPreTokenizer( - r"""'s|'t|'re|'ve|'m|'ll|'d|[\p{L}]+|[\p{N}]|[^\s\p{L}\p{N}]+""", - split_mode=SplitMode.REMOVED, - invert=True, - ), - ByteLevelPreTokenizer(add_prefix_space=False), - ] - ) - - # Set the postprocessor - tokenizer.postprocessor = RobertaPostProcessor( - sep=(eos_token, eos_token_id), cls=(bos_token, bos_token_id), trim_offsets=False, add_prefix_space=False - ) - - parameters = { - "model": "BPE", - "unk_token": unk_token, - "pad_token": pad_token, - "bos_token": bos_token, - "eos_token": eos_token, - "add_prefix_space": add_prefix_space, - "max_length": max_length, - "continuing_subword_prefix": continuing_subword_prefix, - "end_of_word_suffix": end_of_word_suffix, - "trim_offsets": trim_offsets, - } - super().__init__(tokenizer, parameters) diff --git a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/ernie.py b/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/ernie.py deleted file mode 100644 index 6c854faa1713..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/ernie.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from fast_tokenizer import Tokenizer, decoders -from fast_tokenizer.models import FastWordPiece, WordPiece -from fast_tokenizer.normalizers import BertNormalizer -from fast_tokenizer.postprocessors import BertPostProcessor -from fast_tokenizer.pretokenizers import BertPreTokenizer - -from .base_tokenizer import BaseFastTokenizer - -__all__ = ["ErnieFastTokenizer"] - - -class ErnieFastTokenizer(BaseFastTokenizer): - def __init__( - self, - vocab=None, - unk_token="[UNK]", - sep_token="[SEP]", - cls_token="[CLS]", - pad_token="[PAD]", - mask_token="[MASK]", - clean_text=True, - handle_chinese_chars=True, - strip_accents=True, - lowercase=True, - wordpieces_prefix="##", - max_sequence_len=None, - max_input_chars_per_word=100, - use_fast_wordpiece=False, - use_fast_wordpiece_with_pretokenization=False, - ): - tokenizer_model = WordPiece if not use_fast_wordpiece else FastWordPiece - model_kwargs = { - "unk_token": str(unk_token), - "continuing_subword_prefix": wordpieces_prefix, - "max_input_chars_per_word": max_input_chars_per_word, - } - if use_fast_wordpiece: - model_kwargs["with_pretokenization"] = use_fast_wordpiece_with_pretokenization - else: - model_kwargs["handle_chinese_chars"] = handle_chinese_chars - if vocab is not None: - tokenizer = Tokenizer(tokenizer_model(vocab, **model_kwargs)) - else: - tokenizer = Tokenizer(tokenizer_model(**model_kwargs)) - - if tokenizer.token_to_id(str(unk_token)) is not None: - tokenizer.add_special_tokens([str(unk_token)]) - if tokenizer.token_to_id(str(sep_token)) is not None: - tokenizer.add_special_tokens([str(sep_token)]) - if tokenizer.token_to_id(str(cls_token)) is not None: - tokenizer.add_special_tokens([str(cls_token)]) - if tokenizer.token_to_id(str(pad_token)) is not None: - tokenizer.add_special_tokens([str(pad_token)]) - if tokenizer.token_to_id(str(mask_token)) is not None: - tokenizer.add_special_tokens([str(mask_token)]) - - tokenizer.normalizer = BertNormalizer( - clean_text=clean_text, - handle_chinese_chars=handle_chinese_chars, - strip_accents=strip_accents, - lowercase=lowercase, - ) - if not use_fast_wordpiece or not use_fast_wordpiece_with_pretokenization: - tokenizer.pretokenizer = BertPreTokenizer() - - if vocab is not None: - sep_token_id = tokenizer.token_to_id(str(sep_token)) - if sep_token_id is None: - raise TypeError("sep_token not found in the vocabulary") - cls_token_id = tokenizer.token_to_id(str(cls_token)) - if cls_token_id is None: - raise TypeError("cls_token not found in the vocabulary") - - tokenizer.postprocessor = BertPostProcessor((str(sep_token), sep_token_id), (str(cls_token), cls_token_id)) - - tokenizer.decoder = decoders.WordPiece(prefix=wordpieces_prefix) - if max_sequence_len is None: - tokenizer.disable_truncation() - else: - tokenizer.enable_truncation(max_sequence_len) - - parameters = { - "model": "BertWordPiece", - "unk_token": unk_token, - "sep_token": sep_token, - "cls_token": cls_token, - "pad_token": pad_token, - "mask_token": mask_token, - "clean_text": clean_text, - "handle_chinese_chars": handle_chinese_chars, - "strip_accents": strip_accents, - "lowercase": lowercase, - "wordpieces_prefix": wordpieces_prefix, - } - - super().__init__(tokenizer, parameters) diff --git a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/sentencepiece_bpe.py b/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/sentencepiece_bpe.py deleted file mode 100644 index 3daacc6d28c9..000000000000 --- a/fast_tokenizer/python/fast_tokenizer/tokenizers_impl/sentencepiece_bpe.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from .base_tokenizer import BaseFastTokenizer -from fast_tokenizer.models import BPE -from fast_tokenizer.normalizers import NFKCNormalizer -from fast_tokenizer import Tokenizer -from fast_tokenizer.pretokenizers import MetaSpacePreTokenizer - -__all__ = ["SentencePieceBPEFastTokenizer"] - - -class SentencePieceBPEFastTokenizer(BaseFastTokenizer): - def __init__( - self, - vocab=None, - merges=None, - unk_token="", - replacement="▁", - add_prefix_space=True, - dropout=None, - fuse_unk=False, - ): - if vocab is not None and merges is not None: - tokenizer = Tokenizer(BPE(vocab, merges, dropout=dropout, unk_token=unk_token, fuse_unk=fuse_unk)) - else: - tokenizer = Tokenizer(BPE()) - if tokenizer.token_to_id(str(unk_token)) is not None: - tokenizer.add_special_tokens([str(unk_token)]) - tokenizer.normalizer = NFKCNormalizer() - tokenizer.pretokenizer = MetaSpacePreTokenizer(replacement=replacement, add_prefix_space=add_prefix_space) - parameters = { - "model": "SentencePieceBPE", - "unk_token": unk_token, - "replacement": replacement, - "add_prefix_space": add_prefix_space, - "dropout": dropout, - } - - super().__init__(tokenizer, parameters) - - @staticmethod - def from_file(vocab_filename, merges_filename, **kwargs): - vocab, merges = BPE.read_file(vocab_filename, merges_filename) - return SentencePieceBPEFastTokenizer(vocab, merges, **kwargs) diff --git a/fast_tokenizer/python/tests/test_byte_level_pretokenizer.py b/fast_tokenizer/python/tests/test_byte_level_pretokenizer.py deleted file mode 100644 index 9f42234acf9b..000000000000 --- a/fast_tokenizer/python/tests/test_byte_level_pretokenizer.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest - -from fast_tokenizer import pretokenizers - - -class TestByteLevelPreTokenizer(unittest.TestCase): - def setUp(self): - self.pretokenized = pretokenizers.PreTokenizedString("Hello my friend, how is your day going?") - - def check_equals(self, add_prefix_space, use_regex, expected_result): - bytelevel = pretokenizers.ByteLevelPreTokenizer(add_prefix_space=add_prefix_space, use_regex=use_regex) - bytelevel(self.pretokenized) - splits = self.pretokenized.get_splits() - result = [(s, offset) for s, offset, tokens in splits] - self.assertEqual(result, expected_result) - - def test_pretokenize_with_regex(self): - expected_result = [ - ("Hello", (0, 5)), - ("Ġmy", (5, 8)), - ("Ġfriend", (8, 15)), - (",", (15, 16)), - ("Ġhow", (16, 20)), - ("Ġis", (20, 23)), - ("Ġyour", (23, 28)), - ("Ġday", (28, 32)), - ("Ġgoing", (32, 38)), - ("?", (38, 39)), - ] - - self.check_equals(False, True, expected_result) - - def test_pretokenize_without_regex(self): - expected_result = [("HelloĠmyĠfriend,ĠhowĠisĠyourĠdayĠgoing?", (0, 39))] - self.check_equals(False, False, expected_result) - - def test_pretokenize_with_prefix_with_regex(self): - expected_result = [ - ("ĠHello", (0, 5)), - ("Ġmy", (5, 8)), - ("Ġfriend", (8, 15)), - (",", (15, 16)), - ("Ġhow", (16, 20)), - ("Ġis", (20, 23)), - ("Ġyour", (23, 28)), - ("Ġday", (28, 32)), - ("Ġgoing", (32, 38)), - ("?", (38, 39)), - ] - - self.check_equals(True, True, expected_result) - - def test_pretokenize_with_prefix_without_regex(self): - expected_result = [("ĠHelloĠmyĠfriend,ĠhowĠisĠyourĠdayĠgoing?", (0, 39))] - self.check_equals(True, False, expected_result) diff --git a/fast_tokenizer/python/tests/test_clip_tokenizer.py b/fast_tokenizer/python/tests/test_clip_tokenizer.py deleted file mode 100644 index a6124b4f8c44..000000000000 --- a/fast_tokenizer/python/tests/test_clip_tokenizer.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import unittest - -from fast_tokenizer import ClipFastTokenizer, models -from paddlenlp.utils.downloader import get_path_from_url - - -class TestClipFastTokenizer(unittest.TestCase): - def setUp(self): - vocab_path = os.path.join(os.getcwd(), "vocab.json") - merges_path = os.path.join(os.getcwd(), "merges.txt") - if not os.path.exists(vocab_path): - get_path_from_url( - "http://bj.bcebos.com/paddlenlp/models/community/openai/clip-vit-large-patch14/vocab.json", os.getcwd() - ) - if not os.path.exists(merges_path): - get_path_from_url( - "http://bj.bcebos.com/paddlenlp/models/community/openai/clip-vit-large-patch14/merges.txt", os.getcwd() - ) - vocab, merges = models.BPE.read_file(vocab_path, merges_path) - self.tokenizer = ClipFastTokenizer(vocab, merges) - self.expected_ids = [ - 49406, - 320, - 1342, - 272, - 272, - 335, - 273, - 273, - 274, - 16368, - 13439, - 2971, - 748, - 531, - 13610, - 323, - 1896, - 8445, - 323, - 539, - 320, - 2368, - 49407, - ] - self.expected_tokens = [ - "<|startoftext|>", - "a", - "'ll", - "1", - "1", - "p", - "2", - "2", - "3", - "rf", - "âĺĨ", - "ho", - "!!", - "to", - "?'", - "d", - "'d", - "''", - "d", - "of", - "a", - "cat", - "<|endoftext|>", - ] - self.input_text = "A\n'll 11p223RF☆ho!!to?'d'd''d of a cat" - - def test_encode(self): - result = self.tokenizer.encode(self.input_text) - self.assertEqual(result.tokens, self.expected_tokens) - self.assertEqual(result.ids, self.expected_ids) diff --git a/fast_tokenizer/python/tests/test_fast_wordpiece.py b/fast_tokenizer/python/tests/test_fast_wordpiece.py deleted file mode 100644 index 7125aef04ace..000000000000 --- a/fast_tokenizer/python/tests/test_fast_wordpiece.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import unittest - -from fast_tokenizer import ErnieFastTokenizer -from fast_tokenizer.models import WordPiece, FastWordPiece -from paddlenlp.datasets import load_dataset -from paddlenlp.transformers import AutoTokenizer -from paddlenlp.utils.log import logger - -logger.logger.setLevel("ERROR") - - -class TestWordpiece(unittest.TestCase): - def set_flag(self): - self.use_fast_wordpiece = False - self.use_fast_wordpiece_with_pretokenization = False - - def setUp(self): - self.max_seq_length = 128 - self.wordpiece_tokenizer = AutoTokenizer.from_pretrained("ernie-1.0", use_fast=True) - ernie_vocab = self.wordpiece_tokenizer.vocab - self.set_flag() - self.fast_wordpiece_tokenizer = ErnieFastTokenizer( - ernie_vocab, - max_sequence_len=self.max_seq_length, - use_fast_wordpiece=self.use_fast_wordpiece, - use_fast_wordpiece_with_pretokenization=self.use_fast_wordpiece_with_pretokenization, - ) - self.dataset = [example["sentence"] for example in load_dataset("clue", "tnews", splits=["train"])] - - def test_encode(self): - for sentence in self.dataset: - wordpiece_result = self.wordpiece_tokenizer(sentence, max_length=self.max_seq_length) - expected_input_ids = wordpiece_result["input_ids"] - expected_token_type_ids = wordpiece_result["token_type_ids"] - - fast_wordpiece_result = self.fast_wordpiece_tokenizer.encode(sentence) - actual_input_ids = fast_wordpiece_result.ids - actual_token_type_ids = fast_wordpiece_result.type_ids - self.assertEqual(expected_input_ids, actual_input_ids) - self.assertEqual(expected_token_type_ids, actual_token_type_ids) - - def test_get_offset_mapping(self): - for i, sentence in enumerate(self.dataset): - wordpiece_result = self.wordpiece_tokenizer( - sentence, max_length=self.max_seq_length, return_offsets_mapping=True - ) - expected_offset_mapping = wordpiece_result["offset_mapping"] - - fast_wordpiece_result = self.fast_wordpiece_tokenizer.encode(sentence) - actual_offset_mapping = fast_wordpiece_result.offsets - self.assertEqual(expected_offset_mapping, actual_offset_mapping) - - -class TestFastWordpiece(TestWordpiece): - def set_flag(self): - self.use_fast_wordpiece = True - self.use_fast_wordpiece_with_pretokenization = False - - -class TestFastWordpieceWithPretokenization(TestWordpiece): - def set_flag(self): - self.use_fast_wordpiece = True - self.use_fast_wordpiece_with_pretokenization = True - - -class TestFromfile(unittest.TestCase): - def setUp(self): - self.max_seq_length = 128 - t = AutoTokenizer.from_pretrained("ernie-1.0", use_fast=True) - self.vocab_file = t.init_kwargs["vocab_file"] - - def test(self): - WordPiece.from_file(self.vocab_file) - FastWordPiece.from_file(self.vocab_file) - - -if __name__ == "__main__": - unittest.main() diff --git a/fast_tokenizer/python/tests/test_tokenizer_json.py b/fast_tokenizer/python/tests/test_tokenizer_json.py deleted file mode 100644 index ace7f3858d02..000000000000 --- a/fast_tokenizer/python/tests/test_tokenizer_json.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import unittest - -import fast_tokenizer -from fast_tokenizer import ErnieFastTokenizer -from paddlenlp.transformers import AutoTokenizer -from paddlenlp.utils.log import logger - -logger.logger.setLevel("ERROR") - - -class TestTokenizerJson(unittest.TestCase): - def setUp(self): - wordpiece_tokenizer = AutoTokenizer.from_pretrained("ernie-1.0") - ernie_vocab = wordpiece_tokenizer.vocab.token_to_idx - self.fast_tokenizer = ErnieFastTokenizer(ernie_vocab) - - -class TestNormalizerJson(TestTokenizerJson): - def check_normalizer_json(self, normalizer): - self.fast_tokenizer.normalizer = normalizer - json_file = str(normalizer.__class__) + ".json" - self.fast_tokenizer.save(json_file) - tokenizer = ErnieFastTokenizer.from_file(json_file) - os.remove(json_file) - self.assertEqual(normalizer.__getstate__(), tokenizer.normalizer.__getstate__()) - - def test_replace(self): - replace_normalizer = fast_tokenizer.normalizers.ReplaceNormalizer("''", '"') - self.check_normalizer_json(replace_normalizer) - - def test_strip(self): - strip_normalizer = fast_tokenizer.normalizers.StripNormalizer(True, True) - self.check_normalizer_json(strip_normalizer) - - def test_strip_accent(self): - strip_normalizer = fast_tokenizer.normalizers.StripAccentsNormalizer() - self.check_normalizer_json(strip_normalizer) - - def test_nfc(self): - nfc_normalizer = fast_tokenizer.normalizers.NFCNormalizer() - self.check_normalizer_json(nfc_normalizer) - - def test_nfkc(self): - nfkc_normalizer = fast_tokenizer.normalizers.NFKCNormalizer() - self.check_normalizer_json(nfkc_normalizer) - - def test_nfd(self): - nfd_normalizer = fast_tokenizer.normalizers.NFDNormalizer() - self.check_normalizer_json(nfd_normalizer) - - def test_nfkd(self): - nfkd_normalizer = fast_tokenizer.normalizers.NFKDNormalizer() - self.check_normalizer_json(nfkd_normalizer) - - def test_nmt(self): - nmt_normalizer = fast_tokenizer.normalizers.NmtNormalizer() - self.check_normalizer_json(nmt_normalizer) - - def test_lowercase(self): - lowercase_normalizer = fast_tokenizer.normalizers.LowercaseNormalizer() - self.check_normalizer_json(lowercase_normalizer) - - def test_sequence(self): - lowercase_normalizer = fast_tokenizer.normalizers.LowercaseNormalizer() - sequence_normalizer = fast_tokenizer.normalizers.SequenceNormalizer(normalizer_list=[lowercase_normalizer]) - self.check_normalizer_json(sequence_normalizer) - - -if __name__ == "__main__": - unittest.main() diff --git a/fast_tokenizer/run_build_android_armv7_lib.sh b/fast_tokenizer/run_build_android_armv7_lib.sh deleted file mode 100644 index 45303830c0c5..000000000000 --- a/fast_tokenizer/run_build_android_armv7_lib.sh +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -mkdir build_android_armeabi_v7a -cd build_android_armeabi_v7a -cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_ABI="armeabi-v7a" -DANDROID_NATIVE_API_LEVEL=android-21 -DANDROID_STL=c++_shared -DWITH_TESTING=OFF -DWITH_PYTHON=OFF -DANDROID_TOOLCHAIN=clang -make -j8 diff --git a/fast_tokenizer/run_build_android_armv7_lite_lib.sh b/fast_tokenizer/run_build_android_armv7_lite_lib.sh deleted file mode 100644 index 4d09e46108e1..000000000000 --- a/fast_tokenizer/run_build_android_armv7_lite_lib.sh +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -mkdir build_android_armeabi_v7a_lite -cd build_android_armeabi_v7a_lite -cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_ABI="armeabi-v7a" -DANDROID_NATIVE_API_LEVEL=android-21 -DANDROID_STL=c++_shared -DWITH_TESTING=OFF -DWITH_PYTHON=OFF -DANDROID_TOOLCHAIN=clang -DWITH_ICU_LITE=ON -make -j8 diff --git a/fast_tokenizer/run_build_android_armv8_lib.sh b/fast_tokenizer/run_build_android_armv8_lib.sh deleted file mode 100644 index f3905ad31a86..000000000000 --- a/fast_tokenizer/run_build_android_armv8_lib.sh +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -mkdir build_android_arm64_v8a -cd build_android_arm64_v8a -cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_ABI="arm64-v8a" -DANDROID_NATIVE_API_LEVEL=android-21 -DANDROID_STL=c++_shared -DWITH_TESTING=OFF -DWITH_PYTHON=OFF -DANDROID_TOOLCHAIN=clang -make -j8 diff --git a/fast_tokenizer/run_build_android_armv8_lite_lib.sh b/fast_tokenizer/run_build_android_armv8_lite_lib.sh deleted file mode 100644 index 60390b1102a6..000000000000 --- a/fast_tokenizer/run_build_android_armv8_lite_lib.sh +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -mkdir build_android_arm64_v8a_lite -cd build_android_arm64_v8a_lite -cmake .. -DCMAKE_TOOLCHAIN_FILE=$NDK_ROOT/build/cmake/android.toolchain.cmake -DANDROID_ABI="arm64-v8a" -DANDROID_NATIVE_API_LEVEL=android-21 -DANDROID_STL=c++_shared -DWITH_TESTING=OFF -DWITH_PYTHON=OFF -DANDROID_TOOLCHAIN=clang -DWITH_ICU_LITE=ON -make -j8 diff --git a/fast_tokenizer/run_build_cpp_lib.bat b/fast_tokenizer/run_build_cpp_lib.bat deleted file mode 100644 index faf396a27be5..000000000000 --- a/fast_tokenizer/run_build_cpp_lib.bat +++ /dev/null @@ -1,7 +0,0 @@ -if not exist build_cpp mkdir build_cpp -cd build_cpp -for /d %%G in ("*") do rmdir /s /q "%%G" -del /q * -cmake .. -G "Ninja" -DWITH_PYTHON=OFF -DWITH_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -ninja -j20 -cd .. \ No newline at end of file diff --git a/fast_tokenizer/run_build_cpp_lib.sh b/fast_tokenizer/run_build_cpp_lib.sh deleted file mode 100644 index 9e3ccceec678..000000000000 --- a/fast_tokenizer/run_build_cpp_lib.sh +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Can be used in linux and mac -mkdir -p build_cpp -cd build_cpp -rm -rf * -platform="$(uname -s)" -if [[ $platform == Linux* ]]; -then - core_num=`nproc` -else - core_num=`sysctl -n hw.logicalcpu` -fi -echo "Compile with $core_num cores" -cmake .. -DWITH_PYTHON=OFF -DWITH_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -make -j${core_num} - -if [[ $? == 0 ]]; -then - echo "Successfully compile." -else - echo "Fail compiling." -fi -cd .. diff --git a/fast_tokenizer/run_build_py_lib.bat b/fast_tokenizer/run_build_py_lib.bat deleted file mode 100644 index 72bbd6824488..000000000000 --- a/fast_tokenizer/run_build_py_lib.bat +++ /dev/null @@ -1,14 +0,0 @@ -for %%x in (6 7 8 9 10) do ( - if not exist build_py3%%x mkdir build_py3%%x - cd build_py3%%x - for /d %%G in ("*") do rmdir /s /q "%%G" - del /q * - cmake .. -G "Ninja" -DWITH_PYTHON=ON ^ - -DWITH_TESTING=OFF ^ - -DCMAKE_BUILD_TYPE=Release ^ - -DPYTHON_EXECUTABLE=C:\Python3%%x\python.exe ^ - -DPYTHON_INCLUDE_DIR=C:\Python3%%x\include ^ - -DPYTHON_LIBRARY=C:\Python3%%x\libs\python3%%x.lib - ninja -j20 - cd .. -) diff --git a/fast_tokenizer/run_build_py_lib.sh b/fast_tokenizer/run_build_py_lib.sh deleted file mode 100644 index a473cd51a048..000000000000 --- a/fast_tokenizer/run_build_py_lib.sh +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Can be used in linux and mac -# build python lib -mkdir -p build_py36 build_py37 build_py38 build_py39 build_py310 -for py_version in 6 7 8 9 10; -do - cd build_py3${py_version} - rm -rf * - platform="$(uname -s)" - if [[ $platform == Linux* ]]; - then - export LD_LIBRARY_PATH=/opt/_internal/cpython-3.${py_version}.0/lib/:${LD_LIBRARY_PATH} - export PATH=/opt/_internal/cpython-3.${py_version}.0/bin/:${PATH} - core_num=`nproc` - else - export LD_LIBRARY_PATH=/Users/paddle/miniconda2/envs/py3${py_version}/lib/:${LD_LIBRARY_PATH} - export PATH=/Users/paddle/miniconda2/envs/py3${py_version}/bin/:${PATH} - core_num=`sysctl -n hw.logicalcpu` - fi - echo "Compile with $core_num cores" - cmake .. -DWITH_PYTHON=ON -DWITH_TESTING=OFF -DCMAKE_BUILD_TYPE=Release - make -j${core_num} - if [[ $? == 0 ]]; - then - echo "Successfully compile." - else - echo "Fail compiling." - fi - cd .. -done - diff --git a/fast_tokenizer/run_fast_tokenizer_cpp_test.sh b/fast_tokenizer/run_fast_tokenizer_cpp_test.sh deleted file mode 100644 index 02eeda082ed5..000000000000 --- a/fast_tokenizer/run_fast_tokenizer_cpp_test.sh +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -cd ${PWD}/$1 - -for testcase in `ls ${TEST_DIR}`; do - if [[ "${testcase}" == "test"* ]]; then - ${PWD}/${testcase} - if [ $? -ne 0 ]; then - exit -1 - fi - fi -done diff --git a/fast_tokenizer/setup.py b/fast_tokenizer/setup.py deleted file mode 100644 index d172cda979ca..000000000000 --- a/fast_tokenizer/setup.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -from setuptools import Distribution, setup -from setuptools.command.install import install - - -class BinaryDistribution(Distribution): - # when build the package, it will add - # platform name such as "cp37-cp37m-linux_x86_64" - def has_ext_modules(self): - return True - - -class InstallPlatlib(install): - def finalize_options(self): - install.finalize_options(self) - if self.distribution.has_ext_modules(): - self.install_lib = self.install_platlib - - -if os.name != "nt": - package_data = {"fast_tokenizer": ["core_tokenizers.so", "commit.log"]} - package_data["fast_tokenizer.libs"] = [] -else: - package_data = {"fast_tokenizer": ["core_tokenizers.pyd", "core_tokenizers.lib", "commit.log"]} - # Add icu dll - package_data["fast_tokenizer.libs"] = ["icuuc70.dll", "icudt70.dll"] - - -def get_version(): - f = open(os.path.join("python", "fast_tokenizer", "__init__.py")) - lines = f.readlines() - version = "" - for line in lines: - if line.startswith("__version__"): - version = line.split("=")[1] - version = version.strip().replace('"', "") - break - return version - - -long_description = "PaddleNLP Fast Tokenizer Library written in C++ " -setup( - name="fast-tokenizer-python", - version=get_version(), - author="PaddlePaddle Speech and Language Team", - author_email="paddlesl@baidu.com", - description=long_description, - long_description=long_description, - zip_safe=False, - url="https://github.com/PaddlePaddle/PaddleNLP/fast_tokenizer", - package_dir={"": "python"}, - packages=[ - "fast_tokenizer", - "fast_tokenizer.tokenizers_impl", - "fast_tokenizer.normalizers", - "fast_tokenizer.pretokenizers", - "fast_tokenizer.models", - "fast_tokenizer.postprocessors", - "fast_tokenizer.libs", - "fast_tokenizer.decoders", - ], - package_data=package_data, - extras_require={"test": ["pytest>=6.0"]}, - python_requires=">=3.6", - cmdclass={"install": InstallPlatlib}, - license="Apache 2.0", - distclass=BinaryDistribution, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - "Intended Audience :: Education", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", - "Programming Language :: C++", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - ], -) diff --git a/fast_tokenizer/tools/codestyle/clang_format.hook b/fast_tokenizer/tools/codestyle/clang_format.hook deleted file mode 100644 index 1d928216867c..000000000000 --- a/fast_tokenizer/tools/codestyle/clang_format.hook +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -e - -readonly VERSION="3.8" - -version=$(clang-format -version) - -if ! [[ $version == *"$VERSION"* ]]; then - echo "clang-format version check failed." - echo "a version contains '$VERSION' is needed, but get '$version'" - echo "you can install the right version, and make an soft-link to '\$PATH' env" - exit -1 -fi - -clang-format $@ diff --git a/fast_tokenizer/tools/codestyle/copyright.hook b/fast_tokenizer/tools/codestyle/copyright.hook deleted file mode 100644 index ecdc8a23ddeb..000000000000 --- a/fast_tokenizer/tools/codestyle/copyright.hook +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - -import argparse -import io -import re -import sys -import os -import datetime - -COPYRIGHT = '''Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.''' - -def _generate_copyright(comment_mark): - copyright=COPYRIGHT.split(os.linesep) - header = copyright[0].rstrip() - - p = re.search('(\d{4})', header).group(0) - now = datetime.datetime.now() - - header = header.replace(p,str(now.year)) - - ans=[comment_mark + " " + header + os.linesep] - for idx, line in enumerate(copyright[1:]): - ans.append(comment_mark + " " + line.rstrip() + os.linesep) - - return ans - -def _get_comment_mark(path): - lang_type=re.compile(r"\.(py|sh)$") - if lang_type.search(path) is not None: - return "#" - - lang_type=re.compile(r"\.(h|c|hpp|cc|cpp|cu|go|cuh|proto)$") - if lang_type.search(path) is not None: - return "//" - - return None - - -RE_ENCODE = re.compile(r"^[ \t\v]*#.*?coding[:=]", re.IGNORECASE) -RE_COPYRIGHT = re.compile(r".*Copyright \(c\) \d{4}", re.IGNORECASE) -RE_SHEBANG = re.compile(r"^[ \t\v]*#[ \t]?\!") - -def _check_copyright(path): - head=[] - try: - with open(path) as f: - head = [next(f) for x in range(4)] - except StopIteration: - pass - - for idx, line in enumerate(head): - if RE_COPYRIGHT.search(line) is not None: - return True - - return False - -def generate_copyright(path, comment_mark): - original_contents = io.open(path, encoding="utf-8").readlines() - head = original_contents[0:4] - - insert_line_no=0 - for i, line in enumerate(head): - if RE_ENCODE.search(line) or RE_SHEBANG.search(line): - insert_line_no=i+1 - - copyright = _generate_copyright(comment_mark) - if insert_line_no == 0: - new_contents = copyright - if len(original_contents) > 0 and len(original_contents[0].strip()) != 0: - new_contents.append(os.linesep) - new_contents.extend(original_contents) - else: - new_contents=original_contents[0:insert_line_no] - new_contents.append(os.linesep) - new_contents.extend(copyright) - if len(original_contents) > insert_line_no and len(original_contents[insert_line_no].strip()) != 0: - new_contents.append(os.linesep) - new_contents.extend(original_contents[insert_line_no:]) - new_contents="".join(new_contents) - - with io.open(path, 'w') as output_file: - output_file.write(new_contents) - - - -def main(argv=None): - parser = argparse.ArgumentParser( - description='Checker for copyright declaration.') - parser.add_argument('filenames', nargs='*', help='Filenames to check') - args = parser.parse_args(argv) - - retv = 0 - for path in args.filenames: - comment_mark = _get_comment_mark(path) - if comment_mark is None: - print("warning:Unsupported file", path, file=sys.stderr) - continue - - if _check_copyright(path): - continue - - generate_copyright(path, comment_mark) - - -if __name__ == '__main__': - exit(main()) diff --git a/fast_tokenizer/tools/codestyle/cpplint_pre_commit.hook b/fast_tokenizer/tools/codestyle/cpplint_pre_commit.hook deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/fast_tokenizer/tools/codestyle/pylint_pre_commit.hook b/fast_tokenizer/tools/codestyle/pylint_pre_commit.hook deleted file mode 100644 index bf2e982dd88a..000000000000 --- a/fast_tokenizer/tools/codestyle/pylint_pre_commit.hook +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -TOTAL_ERRORS=0 - - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export PYTHONPATH=$DIR:$PYTHONPATH - -# The trick to remove deleted files: https://stackoverflow.com/a/2413151 -for file in $(git diff --name-status | awk '$1 != "D" {print $2}'); do - pylint --disable=all --load-plugins=docstring_checker \ - --enable=doc-string-one-line,doc-string-end-with,doc-string-with-all-args,doc-string-triple-quotes,doc-string-missing,doc-string-indent-error,doc-string-with-returns,doc-string-with-raises $file; - TOTAL_ERRORS=$(expr $TOTAL_ERRORS + $?); -done - -exit $TOTAL_ERRORS -#For now, just warning: -#exit 0 diff --git a/legacy/examples/question_generation/unimo-text/deploy/paddle_serving/README.md b/legacy/examples/question_generation/unimo-text/deploy/paddle_serving/README.md index ad375abab6ed..2bf576da9bdc 100644 --- a/legacy/examples/question_generation/unimo-text/deploy/paddle_serving/README.md +++ b/legacy/examples/question_generation/unimo-text/deploy/paddle_serving/README.md @@ -8,7 +8,6 @@ - [背景介绍](#背景介绍) - [环境准备](#环境准备) - [安装Paddle Serving](#安装paddle-serving) - - [模型转换](#模型转换) - [pipeline部署](#pipeline部署) - [修改配置文件](#修改配置文件) @@ -50,13 +49,6 @@ pip install paddle-serving-server-gpu==0.8.3.post112 # -i https://pypi.tuna.tsin - 如果要安装最新版本的PaddleServing参考[链接](https://github.com/PaddlePaddle/Serving/blob/develop/doc/Latest_Packages_CN.md)。 - - - ## 模型转换 使用Paddle Serving做服务化部署时,需要将保存的inference模型转换为serving易于部署的模型。 diff --git a/model_zoo/bert/README.md b/model_zoo/bert/README.md index d9f032042739..58837afaa301 100644 --- a/model_zoo/bert/README.md +++ b/model_zoo/bert/README.md @@ -273,7 +273,6 @@ python deploy/python/seq_cls_infer.py --model_dir infer_model/ --device gpu --ba 运行后预测结果打印如下: ```bash -[2023-03-02 08:30:03,877] [ INFO] - We are using to load '../../infer_model/'. [INFO] fastdeploy/runtime/runtime.cc(266)::CreatePaddleBackend Runtime initialized with Backend::PDINFER in Device::GPU. Batch id: 0, example id: 0, sentence1: against shimmering cinematography that lends the setting the ethereal beauty of an asian landscape painting, label: positive, negative prob: 0.0003, positive prob: 0.9997. Batch id: 1, example id: 0, sentence1: the situation in a well-balanced fashion, label: positive, negative prob: 0.0002, positive prob: 0.9998. diff --git a/model_zoo/bert/deploy/python/README.md b/model_zoo/bert/deploy/python/README.md index b2f9ef8727ed..e65c589548aa 100644 --- a/model_zoo/bert/deploy/python/README.md +++ b/model_zoo/bert/deploy/python/README.md @@ -4,15 +4,6 @@ 本目录下分别提供 `seq_cls_infer.py` 快速完成在 CPU/GPU 的 GLUE 文本分类任务的 Python 部署示例。 -## 依赖安装 - -直接执行以下命令安装部署示例的依赖。 - -```bash -# 安装 fast_tokenizer 以及 GPU 版本 fastdeploy -pip install fast-tokenizer-python fastdeploy-gpu-python -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html -``` - ## 快速开始 以下示例展示如何基于 FastDeploy 库完成 BERT 模型在 GLUE SST-2 数据集上进行自然语言推断任务的 Python 预测部署,可通过命令行参数`--device`以及`--backend`指定运行在不同的硬件以及推理引擎后端,并使用`--model_dir`参数指定运行的模型,具体参数设置可查看下面[参数说明](#参数说明)。示例中的模型是按照 [BERT 训练文档](../../README.md)导出得到的部署模型,其模型目录为`model_zoo/bert/infer_model`(用户可按实际情况设置)。 @@ -28,7 +19,6 @@ python seq_cls_infer.py --model_dir ../../infer_model/ --device gpu --backend pa 运行完成后返回的结果如下: ```bash -[2023-03-02 08:30:03,877] [ INFO] - We are using to load '../../infer_model/'. [INFO] fastdeploy/runtime/runtime.cc(266)::CreatePaddleBackend Runtime initialized with Backend::PDINFER in Device::GPU. Batch id: 0, example id: 0, sentence1: against shimmering cinematography that lends the setting the ethereal beauty of an asian landscape painting, label: positive, negative prob: 0.0003, positive prob: 0.9997. Batch id: 1, example id: 0, sentence1: the situation in a well-balanced fashion, label: positive, negative prob: 0.0002, positive prob: 0.9998. @@ -49,7 +39,6 @@ Batch id: 4, example id: 0, sentence1: this new jangle of noise , mayhem and stu |--cpu_threads | 当使用cpu推理时,指定推理的cpu线程数,默认为1。| |--backend | 支持的推理后端,可选范围: ['onnx_runtime', 'paddle', 'openvino', 'tensorrt', 'paddle_tensorrt'],默认为'paddle' | |--use_fp16 | 是否使用FP16模式进行推理。使用tensorrt和paddle_tensorrt后端时可开启,默认为False | -|--use_fast| 是否使用FastTokenizer加速分词阶段。默认为True| ## FastDeploy 高阶用法 diff --git a/model_zoo/bert/deploy/python/seq_cls_infer.py b/model_zoo/bert/deploy/python/seq_cls_infer.py index 68f8a1b11357..34105530778d 100644 --- a/model_zoo/bert/deploy/python/seq_cls_infer.py +++ b/model_zoo/bert/deploy/python/seq_cls_infer.py @@ -48,12 +48,6 @@ def parse_arguments(): parser.add_argument("--max_length", type=int, default=128, help="The max length of sequence.") parser.add_argument("--log_interval", type=int, default=10, help="The interval of logging.") parser.add_argument("--use_fp16", type=distutils.util.strtobool, default=False, help="Wheter to use FP16 mode") - parser.add_argument( - "--use_fast", - type=distutils.util.strtobool, - default=True, - help="Whether to use fast_tokenizer to accelarate the tokenization.", - ) return parser.parse_args() @@ -68,7 +62,7 @@ def batchfy_text(texts, batch_size): class Predictor(object): def __init__(self, args): - self.tokenizer = AutoTokenizer.from_pretrained(args.model_dir, use_fast=args.use_fast) + self.tokenizer = AutoTokenizer.from_pretrained(args.model_dir) self.runtime = self.create_fd_runtime(args) self.batch_size = args.batch_size self.max_length = args.max_length diff --git a/model_zoo/ernie-1.0/README.md b/model_zoo/ernie-1.0/README.md index b025325ccb7f..eddb1a05875b 100644 --- a/model_zoo/ernie-1.0/README.md +++ b/model_zoo/ernie-1.0/README.md @@ -80,7 +80,6 @@ Learnt by ERNIE:[mask] [mask] [mask] 是黑龙江的省会,国际 [mask] [ma ├── run_gb512_s1m_static.sh ├── run_gb512_s1m_trainer.sh ├── run_pretrain.py 训练启动python脚本 -├── run_pretrain_static.py └── run_pretrain_trainer.py ``` @@ -653,7 +652,6 @@ python deploy/seq_cls_infer.py --model_dir tmp/chnsenticorp_v2/export/ --device 运行后预测结果打印如下: ```text -[2023-03-01 08:25:31,352] [ INFO] - We are using to load '../tmp/chnsenticorp_v2/export/'. WARNING: Logging before InitGoogleLogging() is written to STDERR W0301 08:25:37.617117 58742 analysis_config.cc:958] It is detected that mkldnn and memory_optimize_pass are enabled at the same time, but they are not supported yet. Currently, memory_optimize_pass is explicitly disabled [INFO] fastdeploy/runtime/runtime.cc(266)::CreatePaddleBackend Runtime initialized with Backend::PDINFER in Device::CPU. diff --git a/model_zoo/ernie-1.0/finetune/deploy/README.md b/model_zoo/ernie-1.0/finetune/deploy/README.md index d4e135253ebb..cede7f79d2a0 100644 --- a/model_zoo/ernie-1.0/finetune/deploy/README.md +++ b/model_zoo/ernie-1.0/finetune/deploy/README.md @@ -4,15 +4,6 @@ 本目录下提供 `seq_cls_infer.py` 快速完成在 CPU/GPU 的中文情感分类任务的 Python 部署示例。 -## 依赖安装 - -直接执行以下命令安装部署示例的依赖。 - -```bash -# 安装 fast_tokenizer 以及 GPU 版本 fastdeploy -pip install fast-tokenizer-python fastdeploy-gpu-python -f https://www.paddlepaddle.org.cn/whl/fastdeploy.html -``` - ## 快速开始 以下示例展示如何基于 FastDeploy 库完成 ERNIE 1.0 模型在 ChnSenticorp 数据集上进行文本分类任务的 Python 预测部署,可通过命令行参数`--device`以及`--backend`指定运行在不同的硬件以及推理引擎后端,并使用`--model_dir`参数指定运行的模型,具体参数设置可查看下面[参数说明](#参数说明)。示例中的模型是按照 [ERNIE 1.0 训练文档](../../README.md)导出得到的部署模型,其模型目录为`model_zoo/ernie-1.0/finetune/tmp/export`(用户可按实际情况设置)。 @@ -28,7 +19,6 @@ python seq_cls_infer.py --model_dir ../tmp/chnsenticorp_v2/export/ --device gpu 运行完成后返回的结果如下: ```bash -[2023-02-26 13:38:46,370] [ INFO] - We are using to load '../../finetune/tmp/chnsenticorp_v2/export/'. [INFO] fastdeploy/runtime/runtime.cc(266)::CreatePaddleBackend Runtime initialized with Backend::PDINFER in Device::GPU. Batch id: 0, example id: 0, sentence: 这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般, label: negative, negative prob: 0.9999, positive prob: 0.0001. Batch id: 1, example id: 0, sentence: 怀着十分激动的心情放映,可是看着看着发现,在放映完毕后,出现一集米老鼠的动画片!开始还怀疑是不是赠送的个别现象,可是后来发现每张DVD后面都有!真不知道生产商怎么想的,我想看的是猫和老鼠,不是米老鼠!如果厂家是想赠送的话,那就全套米老鼠和唐老鸭都赠送,只在每张DVD后面添加一集算什么??简直是画蛇添足!!, label: negative, negative prob: 0.9998, positive prob: 0.0002. @@ -48,7 +38,6 @@ Batch id: 2, example id: 0, sentence: 还稍微重了点,可能是硬盘大的 |--cpu_threads | 当使用cpu推理时,指定推理的cpu线程数,默认为1。| |--backend | 支持的推理后端,可选范围: ['onnx_runtime', 'paddle', 'openvino', 'tensorrt', 'paddle_tensorrt'],默认为'paddle' | |--use_fp16 | 是否使用FP16模式进行推理。使用tensorrt和paddle_tensorrt后端时可开启,默认为False | -|--use_fast| 是否使用FastTokenizer加速分词阶段。默认为True| ## FastDeploy 高阶用法 diff --git a/model_zoo/ernie-1.0/finetune/deploy/seq_cls_infer.py b/model_zoo/ernie-1.0/finetune/deploy/seq_cls_infer.py index 5232e09505b3..2ba53e76b24c 100644 --- a/model_zoo/ernie-1.0/finetune/deploy/seq_cls_infer.py +++ b/model_zoo/ernie-1.0/finetune/deploy/seq_cls_infer.py @@ -49,12 +49,6 @@ def parse_arguments(): parser.add_argument("--use_fp16", type=distutils.util.strtobool, default=False, help="Wheter to use FP16 mode") parser.add_argument("--cpu_threads", type=int, default=1, help="Number of threads to predict when using cpu.") parser.add_argument("--device_id", type=int, default=0, help="Select which gpu device to train model.") - parser.add_argument( - "--use_fast", - type=distutils.util.strtobool, - default=True, - help="Whether to use fast_tokenizer to accelarate the tokenization.", - ) return parser.parse_args() @@ -69,7 +63,7 @@ def batchfy_text(texts, batch_size): class Predictor(object): def __init__(self, args): - self.tokenizer = AutoTokenizer.from_pretrained(args.model_dir, use_fast=args.use_fast) + self.tokenizer = AutoTokenizer.from_pretrained(args.model_dir) self.runtime = self.create_fd_runtime(args) self.batch_size = args.batch_size self.max_length = args.max_length diff --git a/model_zoo/ernie-1.0/run_gb512_s1m_static.sh b/model_zoo/ernie-1.0/run_gb512_s1m_static.sh deleted file mode 100644 index 23e885ca5fed..000000000000 --- a/model_zoo/ernie-1.0/run_gb512_s1m_static.sh +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -x -unset CUDA_VISIBLE_DEVICES - -rm -rf *.prototxt -rm -rf core.* -rm -rf start_sharding* -rm -rf main_sharding* - -# dp8 for 8 worker of data parallel -# gb512 for the global batch size is 512 = 64 * 8 -task_name="ernie-1.0-dp8-gb512" -rm -rf output/$task_name/log - -python -u -m paddle.distributed.launch \ - --gpus "0,1,2,3,4,5,6,7" \ - --log_dir "output/$task_name/log" \ - run_pretrain_static.py \ - --model_type "ernie" \ - --model_name_or_path "ernie-1.0-base-zh" \ - --tokenizer_name_or_path "ernie-1.0-base-zh" \ - --input_dir "./data" \ - --data_impl "mmap" \ - --output_dir "output/$task_name" \ - --split 949,50,1 \ - --max_seq_len 512 \ - --micro_batch_size 64 \ - --sharding_degree 1\ - --dp_degree 8 \ - --use_sharding false \ - --use_amp true \ - --use_recompute false \ - --max_lr 0.0001 \ - --min_lr 0.00001 \ - --max_steps 1000000 \ - --save_steps 50000 \ - --checkpoint_steps 5000 \ - --decay_steps 990000 \ - --weight_decay 0.01\ - --warmup_rate 0.01 \ - --grad_clip 1.0 \ - --num_workers 2 \ - --logging_freq 20\ - --eval_freq 1000 \ - --device "gpu" - -# NOTE: please set use_sharding=True for sharding_degree > 1 diff --git a/model_zoo/ernie-1.0/run_pretrain_static.py b/model_zoo/ernie-1.0/run_pretrain_static.py deleted file mode 100644 index 0986e01039b2..000000000000 --- a/model_zoo/ernie-1.0/run_pretrain_static.py +++ /dev/null @@ -1,744 +0,0 @@ -# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -ERNIE pretraining scripts for paddlepaddle static graph mode. -""" -import collections -import json -import os -import random -import shutil -import time - -import numpy as np -import paddle -import paddle.distributed as dist -import paddle.distributed.fleet as fleet -import yaml -from args import parse_args -from data_tools.dataset_utils import build_train_valid_test_datasets -from paddle.distributed.fleet.meta_optimizers.sharding.utils import save_persistables -from visualdl import LogWriter - -from paddlenlp.data import Stack -from paddlenlp.ops import Topology, get_rng_state_tracker -from paddlenlp.transformers import ( - ErnieConfig, - ErnieForPretraining, - ErniePretrainingCriterion, - ErnieTokenizer, - LinearAnnealingWithWarmupDecay, -) -from paddlenlp.utils.batch_sampler import DistributedBatchSampler -from paddlenlp.utils.log import logger - -MODEL_CLASSES = { - "ernie": (ErnieConfig, ErnieForPretraining, ErniePretrainingCriterion, ErnieTokenizer), -} - - -def create_pretrained_dataset( - args, - data_file, - tokenizer, - data_world_size, - data_world_rank, - max_seq_len, - places, - data_holders, - binary_head=True, - current_step=0, -): - - train_valid_test_num_samples = [ - args.global_batch_size * args.max_steps, - args.micro_batch_size * (args.max_steps // args.eval_freq + 1) * args.eval_iters * data_world_size, - args.micro_batch_size * args.test_iters * data_world_size, - ] - train_ds, valid_ds, test_ds = build_train_valid_test_datasets( - data_prefix=data_file, - args=args, - tokenizer=tokenizer, - splits_string=args.split, - train_valid_test_num_samples=train_valid_test_num_samples, - max_seq_length=args.max_seq_len, - masked_lm_prob=args.masked_lm_prob, - short_seq_prob=args.short_seq_prob, - seed=args.seed, - skip_warmup=True, - binary_head=binary_head, - max_seq_length_dec=None, - dataset_type="ernie", - ) - - def print_dataset(data, mode="train"): - logger.info(f"Sample data for {mode} mode") - input_ids, segment_ids, input_mask, masked_lm_positions, masked_lm_labels, next_sentence_labels = data - if tokenizer.pad_token_id in input_ids: - input_ids = input_ids[0 : list(input_ids).index(tokenizer.pad_token_id)] - logger.info(tokenizer._decode(input_ids)) - for pos, label in zip(masked_lm_positions, masked_lm_labels): - input_ids[pos] = label - logger.info(tokenizer._decode(input_ids)) - logger.info(tokenizer.convert_ids_to_tokens(masked_lm_labels)) - - print_dataset(train_ds[0], "train") - print_dataset(valid_ds[0], "valid") - print_dataset(test_ds[0], "test") - - def _collate_data(data, stack_fn=Stack()): - num_fields = len(data[0]) - out = [None] * num_fields - # 0. input_ids, - # 1. segment_ids, - # 2. input_mask, - # 3. masked_lm_positions, - # 4. masked_lm_labels, - # 5. next_sentence_labels - for i in (0, 1, 2, 5): - out[i] = stack_fn([x[i] for x in data]) - out[5] = out[5].reshape([-1, 1]) - _, seq_length = out[0].shape - size = sum(len(x[3]) for x in data) - # masked_lm_positions - # Organize as a 1D tensor for gather or use gather_nd - if size % 8 != 0: - size += 8 - (size % 8) - out[3] = np.full(size, 0, dtype=np.int32) - # masked_lm_labels - out[4] = np.full([size, 1], -1, dtype=np.int64) - mask_token_num = 0 - for i, x in enumerate(data): - for j, pos in enumerate(x[3]): - out[3][mask_token_num] = i * seq_length + pos - out[4][mask_token_num] = x[4][j] - mask_token_num += 1 - - return out - - def loader(dataset, consumed_samples=0): - batch_sampler = DistributedBatchSampler( - dataset, - batch_size=args.micro_batch_size, - num_replicas=data_world_size, - rank=data_world_rank, - shuffle=False, - drop_last=True, - consumed_samples=consumed_samples, - ) - data_loader = paddle.io.DataLoader( - dataset=dataset, - places=places, - feed_list=data_holders, - batch_sampler=batch_sampler, - num_workers=args.num_workers, - worker_init_fn=None, - collate_fn=_collate_data, - return_list=False, - ) - return data_loader - - train_dl = loader(train_ds, args.global_batch_size * current_step) - valid_dl = loader( - valid_ds, args.micro_batch_size * ((current_step + 1) // args.eval_freq) * args.eval_iters * data_world_size - ) - test_dl = loader(test_ds, 0) - - return train_dl, valid_dl, test_dl - - -def create_data_holder(args=None): - input_ids = paddle.static.data(name="input_ids", shape=[-1, -1], dtype="int64") - segment_ids = paddle.static.data(name="segment_ids", shape=[-1, -1], dtype="int64") - input_mask = paddle.static.data(name="input_mask", shape=[-1, 1, 1, -1], dtype="float32") - masked_lm_positions = paddle.static.data(name="masked_lm_positions", shape=[-1], dtype="int32") - masked_lm_labels = paddle.static.data(name="masked_lm_labels", shape=[-1, 1], dtype="int64") - - next_sentence_labels = paddle.static.data(name="next_sentence_labels", shape=[-1, 1], dtype="int64") - - return [input_ids, segment_ids, input_mask, masked_lm_positions, masked_lm_labels, next_sentence_labels] - - -def dist_optimizer(args, topo): - default_global_batch_size = topo.data_info.size * args.micro_batch_size - if args.global_batch_size is None: - args.global_batch_size = default_global_batch_size - - bsz_per_dp = args.global_batch_size // topo.data_info.size - micro_batch_size = args.micro_batch_size - assert ( - args.global_batch_size % micro_batch_size == 0 - ), "cannot do gradient accumulate, global_batch_size: {} micro_batch_size: {}".format( - args.global_batch_size, micro_batch_size - ) - accumulate_steps = bsz_per_dp // micro_batch_size - - exec_strategy = paddle.static.ExecutionStrategy() - exec_strategy.num_threads = 1 - exec_strategy.num_iteration_per_drop_scope = 10000 - - build_strategy = paddle.static.BuildStrategy() - build_strategy.enable_sequential_execution = True # for profile - # build_strategy.reduce_strategy = paddle.static.BuildStrategy.ReduceStrategy._NoReduce - build_strategy.fuse_broadcast_ops = True - build_strategy.fix_op_run_order = True - build_strategy.enable_inplace = True - build_strategy.enable_addto = args.enable_addto - - dist_strategy = fleet.DistributedStrategy() - # dist_strategy.without_graph_optimization = True - dist_strategy.execution_strategy = exec_strategy - dist_strategy.build_strategy = build_strategy - dist_strategy.nccl_comm_num = 3 - dist_strategy.fuse_grad_size_in_MB = 16 - - dist_strategy.recompute = args.use_recompute - dist_strategy.pipeline = args.pp_degree > 1 - - if args.pp_degree <= 1 and args.sharding_degree <= 1 and accumulate_steps > 1: - dist_strategy.gradient_merge = True - dist_strategy.gradient_merge_configs = {"k_steps": accumulate_steps} - args.eval_iters *= accumulate_steps - args.test_iters *= accumulate_steps - - if args.use_amp: - dist_strategy.amp = True - dist_strategy.amp_configs = { - "custom_white_list": [ - "softmax", - "layer_norm", - "gelu", - ], - "custom_black_list": ["c_softmax_with_cross_entropy"], - "init_loss_scaling": 32768, - "use_dynamic_loss_scaling": True, - } - if args.use_sharding: - dist_strategy.sharding = True - dist_strategy.sharding_configs = { - "segment_broadcast_MB": 32, - "sharding_degree": args.sharding_degree, - "mp_degree": args.mp_degree, - "pp_degree": args.pp_degree, - "dp_degree": args.dp_degree, - "gradient_merge_acc_step": accumulate_steps if args.sharding_degree > 1 else 1, - "optimize_offload": False, - } - if args.pp_degree > 1: - dist_strategy.pipeline_configs = { - "schedule_mode": "1F1B", - "micro_micro_batch_size": micro_batch_size, - "accumulate_steps": accumulate_steps, - } - - args.accumulate_steps = accumulate_steps - return dist_strategy - - -def get_train_data_file(args): - if len(args.input_dir.split()) > 1: - # weight-1 data-prefix-1 weight-2 data-prefix-2 ... - return args.input_dir.split() - else: - files = [ - os.path.join(args.input_dir, f) - for f in os.listdir(args.input_dir) - if (os.path.isfile(os.path.join(args.input_dir, f)) and "_idx.npz" in str(f)) - ] - files = [x.replace("_idx.npz", "") for x in files] - - if len(files) > 1: - ret = [] - logger.info("You are using multi-dataset:") - for x in files: - ret.append(1.0) - ret.append(x) - logger.info(" > set weight of %s dataset to 1.0" % x) - return ret - - return files - - -def run_evaluate( - data_loader, exe, program, iter_steps, log_writer, global_step, args, is_last, eval_fetch, task_name="valid" -): - all_ret = collections.defaultdict(list) - average_ret = collections.defaultdict(float) - - local_time = time.time() - worker_num = fleet.worker_num() - - for eval_step, batch in enumerate(data_loader): - ret = exe.run(program, feed=batch, fetch_list=list(eval_fetch.values())) - if is_last: - for k, v in zip(list(eval_fetch.keys()), ret): - all_ret[k].append(v.item()) - - if eval_step >= iter_steps - 1: - if not is_last or log_writer is None: - break - - for k in list(eval_fetch.keys()): - average_ret[k] = sum(all_ret[k]) / len(all_ret[k]) / worker_num - - speed = iter_steps / (time.time() - local_time) - speed_tokens = speed * args.micro_batch_size * args.max_seq_len * worker_num - ips = speed * args.micro_batch_size * worker_num - - loss_info = ", ".join(["{}: {:.6f}".format(k, average_ret[k]) for k in eval_fetch.keys()]) - - logger.info( - "%s step %d, batch: %d, %s, speed: %.0f tokens/s, ips: %.2f seqs/s" - % (task_name, global_step, eval_step + 1, loss_info, speed_tokens, ips) - ) - - for k in list(eval_fetch.keys()): - log_writer.add_scalar("%s/%s" % (task_name, k), average_ret[k], global_step) - - break - - -def all_reduce(v): - if fleet.worker_num() <= 1: - return v - v = v + 0 - dist.all_reduce(v) - return v - - -def default_logdir() -> str: - """ - Same default - """ - import socket - from datetime import datetime - - current_time = datetime.now().strftime("%b%d_%H-%M-%S") - return os.path.join("runs", current_time + "_" + socket.gethostname()) - - -def do_train(args): - # Initialize the paddle and paddle fleet execute environment - paddle.enable_static() - fleet.init(is_collective=True) - - # Create the random seed for the worker - random.seed(args.seed) - np.random.seed(args.seed) - paddle.seed(args.seed) - get_rng_state_tracker().add("global_seed", args.seed) - get_rng_state_tracker().add("local_seed", args.seed + fleet.worker_index() + 2021) - - assert args.device in ["cpu", "gpu", "xpu", "mlu"], "Invalid device! Available device should be cpu, gpu, or xpu." - place = paddle.set_device(args.device) - - worker_num = fleet.worker_num() - worker_index = fleet.worker_index() - - assert ( - args.dp_degree * args.sharding_degree * args.mp_degree * args.pp_degree == worker_num - ), "The product of degree num should be equal to worker_num." - - topo = Topology( - device_rank=worker_index, - world_size=worker_num, - dp_degree=args.dp_degree, - pp_degree=args.pp_degree, - sharding_degree=args.sharding_degree, - mp_degree=args.mp_degree, - ) - - logger.info("The topo of hybrid parallelism:\n{}".format(topo)) - - dist_strategy = dist_optimizer(args, topo) - - # Create log write, train results show on last card of pipeline. - # Create log write, - log_writer = None - if worker_index == 0: - log_writer = LogWriter(os.path.join(args.output_dir, default_logdir())) - - # Define the input data in the static mode - config_class, model_class, criterion_class, tokenizer_class = MODEL_CLASSES[args.model_type] - pretrained_models_list = list(model_class.pretrained_init_configuration.keys()) - - # load config in checkpoint - global_step = 0 - checkpoint_dir = os.path.join(args.output_dir, "model_last") - if os.path.exists(checkpoint_dir): - if os.path.isfile(os.path.join(checkpoint_dir, "./config.yml")): - with open(os.path.join(checkpoint_dir, "./config.yml"), "r") as f: - step_config = yaml.load(f, Loader=yaml.FullLoader) - assert ( - step_config["global_batch_size"] == args.global_batch_size - ), "Please ensure checkpoint global batch size is the same. Folder: {}".format(checkpoint_dir) - global_step = step_config["global_step"] - - data_file = get_train_data_file(args) - main_program = paddle.static.default_main_program() - startup_program = paddle.static.default_startup_program() - with paddle.static.program_guard(main_program, startup_program): - data_holders = create_data_holder(args) - # 0. input_ids, - # 1. segment_ids, - # 2. input_mask, - # 3. masked_lm_positions, - # 4. masked_lm_labels, - # 5. next_sentence_labels - - [ - input_ids, - segment_ids, - input_mask, - masked_lm_positions, - masked_lm_labels, - next_sentence_labels, - ] = data_holders - - tokenizer = tokenizer_class.from_pretrained(args.tokenizer_name_or_path) - tokenizer.extend_chinese_char() - - train_data_loader, valid_data_loader, test_data_loader = create_pretrained_dataset( - args, - data_file, - tokenizer, - data_world_size=topo.data_info.size, - data_world_rank=topo.data_info.rank, - max_seq_len=args.max_seq_len, - places=paddle.static.cuda_places(), - data_holders=data_holders, - binary_head=args.binary_head, - current_step=global_step, - ) - fleet.init(is_collective=True) - - if args.model_name_or_path in pretrained_models_list: - model_config = model_class.pretrained_init_configuration[args.model_name_or_path] - if model_config["vocab_size"] % 8 != 0: - model_config["vocab_size"] += 8 - (model_config["vocab_size"] % 8) - model_config["hidden_dropout_prob"] = args.hidden_dropout_prob - model_config["attention_probs_dropout_prob"] = args.attention_probs_dropout_prob - model = model_class(config_class(**model_config)) - else: - model, _ = model_class.from_pretrained( - args.model_name_or_path, - hidden_dropout_prob=args.hidden_dropout_prob, - attention_probs_dropout_prob=args.attention_probs_dropout_prob, - ) - - # Create the model for the gpt pretrain - prediction_scores, seq_relationship_score = model( - input_ids=input_ids, - token_type_ids=segment_ids, - position_ids=None, - attention_mask=input_mask, - masked_positions=masked_lm_positions, - ) - - criterion = criterion_class(with_nsp_loss=args.binary_head) - if args.binary_head: - lm_loss, sop_loss = criterion( - prediction_scores, seq_relationship_score, masked_lm_labels, next_sentence_labels - ) - loss = lm_loss + sop_loss - lm_loss_reduce = all_reduce(lm_loss) - sop_loss_reduce = all_reduce(sop_loss) - else: - loss = criterion(prediction_scores, seq_relationship_score, masked_lm_labels) - - loss_reduce = all_reduce(loss) - - # Create the learning_rate sheduler and optimizer - if args.decay_steps is None: - args.decay_steps = args.max_steps - - lr_scheduler = LinearAnnealingWithWarmupDecay( - args.max_lr, - args.min_lr, - warmup_step=args.warmup_rate * args.max_steps, - decay_step=args.decay_steps, - last_epoch=global_step, - ) - - clip = None - if args.grad_clip > 0: - clip = paddle.nn.ClipGradByGlobalNorm(clip_norm=args.grad_clip) - - decay_param = [p.name for n, p in model.named_parameters() if not any(nd in n for nd in ["bias", "norm"])] - logger.info("Using paddle.optimizer.AdamW.") - optimizer = paddle.optimizer.AdamW( - learning_rate=lr_scheduler, - beta1=args.adam_beta1, - beta2=args.adam_beta2, - epsilon=args.adam_epsilon, - grad_clip=clip, - weight_decay=args.weight_decay, - apply_decay_param_fun=lambda x: x in decay_param, - ) - # alias - optimizer.apply_optimize = optimizer._apply_optimize - - # if args.use_recompute: - # dist_strategy.recompute = True - # dist_strategy.recompute_configs = { - # "checkpoints": model.ernie.checkpoints - # } - - # Use the fleet api to compile the distributed optimizer - optimizer = fleet.distributed_optimizer(optimizer, strategy=dist_strategy) - - optimizer.minimize(loss) - logger.info(f"final strategy: {fleet._final_strategy()}") - logger.info("The training meta optimizer is/are %s" % fleet._get_applied_meta_list()) - - program_desc_dir = os.path.join(args.output_dir, "program_desc") - if not os.path.isdir(program_desc_dir): - os.mkdir(program_desc_dir) - - with open(program_desc_dir + "/main_program.txt.%d" % worker_index, "w") as f: - f.write(str(main_program)) - - with open(program_desc_dir + "/startup_program.txt.%d" % worker_index, "w") as f: - f.write(str(startup_program)) - - if worker_index == 0: - # log the model config and args - model_config_json = json.dumps(model.config.to_dict(), ensure_ascii=False, indent=2) - log_writer.add_text("model_config", model_config_json) - args_dict = {"paddle commit id": str(paddle.version.commit)} - for arg in vars(args): - args_dict[arg] = str(getattr(args, arg)) - log_writer.add_text("args", json.dumps(args_dict, indent=2)) - - # Define the Executor for running the static model - exe = paddle.static.Executor(place) - exe.run(startup_program) - - test_program = main_program.clone(for_test=True) - - if args.model_name_or_path not in pretrained_models_list: - logger.info("Try to load checkpoint from %s " % args.model_name_or_path) - dygrah_path = os.path.join(args.model_name_or_path, "model_state.pdparams") - static_path = os.path.join(args.model_name_or_path, "static_vars") - - flag_loaded = False - if os.path.exists(static_path): - if args.mp_degree > 1: - logger.warning("MP should init with dygraph params") - else: - logger.info("Loading parameters from %s" % static_path) - paddle.static.load(main_program, static_path, exe) - flag_loaded = True - - if not flag_loaded and os.path.exists(dygrah_path): - if args.sharding_degree > 1: - logger.warning("Sharding should init with static vars") - else: - logger.info("Loading parameters from %s" % dygrah_path) - # init_static_with_params(model, paddle.load(dygrah_path, return_numpy=True), topo, main_program) - flag_loaded = True - - if not flag_loaded: - logger.error("No checkpoint load.") - - # load checkpoint vars - if os.path.exists(checkpoint_dir): - if os.path.isfile(os.path.join(checkpoint_dir, "./config.yml")): - paddle.static.load(main_program, os.path.join(checkpoint_dir, "static_vars"), exe) - - fetch_loss_vars = collections.OrderedDict() - fetch_other_vars = collections.OrderedDict() - fetch_loss_vars["loss"] = loss_reduce - if args.binary_head: - fetch_loss_vars["lm_loss"] = lm_loss_reduce - fetch_loss_vars["sop_loss"] = sop_loss_reduce - - fetch_other_vars["learning_rate"] = main_program.global_block().vars["learning_rate_0"] - - additional_vars = collections.OrderedDict() - if args.use_amp: - for key in ["loss_scaling", "num_good_steps", "num_bad_steps"]: - additional_vars[key] = main_program.global_block().vars[key + "_0"] - - tic_train = time.time() - while True: - fetchs = [] - fetchs_keys = [] - if topo.is_last: - fetchs = list(fetch_loss_vars.values()) + list(fetch_other_vars.values()) + list(additional_vars.values()) - fetchs_keys = list(fetch_loss_vars.keys()) + list(fetch_other_vars.keys()) + list(additional_vars.keys()) - - # Bug fix, if not call valid_data_loader, the enumerate will call valid_data_loader - # many times. and start a new random dataloader. - valid_data_loader = valid_data_loader() - test_data_loader = test_data_loader() - - loss_res = collections.defaultdict(list) - for step, batch in enumerate(train_data_loader()): - ret = exe.run(main_program, feed=batch, fetch_list=fetchs, use_program_cache=True) - # Skip for accumulate_steps in global step - - if log_writer is not None: - for k, v in zip(fetchs_keys, ret): - if k in fetch_loss_vars: - loss_res[k].append(v.item()) - - if (step + 1) % args.accumulate_steps != 0: - continue - global_step += 1 - # In the new 2.0 api, must call this function to change the learning_rate - lr_scheduler.step() - - if global_step % args.logging_freq == 0: - if topo.is_last and log_writer is not None: - res = collections.defaultdict(float) - for k, v in zip(fetchs_keys, ret): - if k in fetch_loss_vars: - res[k] = sum(loss_res[k]) / len(loss_res[k]) / worker_num - loss_res[k] = [] - else: - res[k] = v - - speed = args.logging_freq / (time.time() - tic_train) - res["global_step"] = global_step - res["steps_per_second"] = speed - res["samples_per_second"] = speed * args.global_batch_size - - loss_info = ", ".join(["{}: {:.6f}".format(k, res[k]) for k in fetch_loss_vars.keys()]) - - common_loginfo = ( - "global step %d, %s, speed: %.2f steps/s, ips: %.2f seqs/s, learning rate: %.5e" - % ( - global_step, - loss_info, - res["steps_per_second"], - res["samples_per_second"], - res["learning_rate"], - ) - ) - additional_loginfo = ", ".join(["{}: {}".format(k, res[k]) for k in additional_vars.keys()]) - if additional_loginfo: - common_loginfo += ", " + additional_loginfo - logger.info(common_loginfo) - - for k, v in res.items(): - if k in additional_vars: - log_writer.add_scalar("amp/" + k, v, global_step) - else: - log_writer.add_scalar("train/" + k, v, global_step) - - tic_train = time.time() - - # if args.check_accuracy: - # if global_step >= args.max_steps: - # return - # else: - # continue - - if global_step % args.eval_freq == 0: - # TODO, check the input data of validation - eval_fetch = collections.OrderedDict() - # if topo.is_last: - eval_fetch["loss"] = loss_reduce - if args.binary_head: - eval_fetch["lm_loss"] = lm_loss_reduce - eval_fetch["sop_loss"] = sop_loss_reduce - - run_evaluate( - valid_data_loader, - exe, - test_program, - args.eval_iters, - log_writer, - global_step, - args, - topo.is_last, - eval_fetch, - "valid", - ) - tic_train = time.time() - - if global_step % args.save_steps == 0 or global_step >= args.max_steps: - output_dir = os.path.join(args.output_dir, "model_%d" % global_step) - logger.debug("saving models to {}".format(output_dir)) - save_persistables(exe, os.path.join(output_dir, "static_vars"), main_program) - tokenizer.save_pretrained(output_dir) - tic_train = time.time() - - if global_step % args.checkpoint_steps == 0: - output_dir = os.path.join(args.output_dir, "model_last") - if worker_index == 0: - if not os.path.exists(output_dir): - os.mkdir(output_dir) - output_dir_bak = os.path.join(args.output_dir, "model_last_bak") - if os.path.exists(output_dir): - if os.path.exists(output_dir_bak): - shutil.rmtree(output_dir_bak) - shutil.move(output_dir, output_dir_bak) - os.mkdir(output_dir) - - step_config = { - "model_name": args.model_name_or_path, - "global_step": global_step, - "global_batch_size": args.global_batch_size, - "consumed_samples": global_step * args.global_batch_size, - } - - with open(os.path.join(output_dir, "config.yml"), "w") as f: - yaml.dump(step_config, f, encoding="utf-8", allow_unicode=True) - - fleet.barrier_worker() - - logger.debug("saving models to {}".format(output_dir)) - if args.sharding_degree <= 1: - # Save on the first worker by default. - if worker_index == 0: - paddle.static.save(main_program, os.path.join(output_dir, "static_vars")) - else: - # Use save_persistables in sharding, but more slower - save_persistables(exe, os.path.join(output_dir, "static_vars"), main_program) - - if global_step >= args.max_steps: - eval_fetch = collections.OrderedDict() - # if topo.is_last: - eval_fetch["loss"] = loss_reduce - if args.binary_head: - eval_fetch["lm_loss"] = lm_loss_reduce - eval_fetch["sop_loss"] = sop_loss_reduce - - run_evaluate( - test_data_loader, - exe, - test_program, - args.test_iters, - log_writer, - global_step, - args, - topo.is_last, - eval_fetch, - "test", - ) - del train_data_loader - del valid_data_loader - del test_data_loader - return - - -if __name__ == "__main__": - args = parse_args(MODEL_CLASSES) - logger.info("{:20}:{}".format("paddle commit id", paddle.version.commit)) - for arg in vars(args): - logger.info("{:20}:{}".format(arg, getattr(args, arg))) - - do_train(args) diff --git a/model_zoo/ernie-3.0-tiny/README.md b/model_zoo/ernie-3.0-tiny/README.md index c0107eea0ca8..f6239638a3d0 100644 --- a/model_zoo/ernie-3.0-tiny/README.md +++ b/model_zoo/ernie-3.0-tiny/README.md @@ -20,7 +20,7 @@ - **ERNIE 3.0 Tiny** 百度 ERNIE 使用 ERNIE-Tiny 系列的知识蒸馏技术,将 ERNIE 3.0 Titan 大模型的能力传递给小模型,产出并开源了易于部署的 ERNIE 3.0 Tiny 系列预训练模型,刷新了中文小模型的 SOTA 成绩。在这些较少参数量的 ERNIE 3.0 Tiny 系列模型中,有一部分可以直接部署在 CPU 上。 -- **端上语义理解压缩方案** 在语义理解任务中使用 ERNIE 3.0 Tiny 微调的基础上,我们建议进一步使用包含模型裁剪、量化训练、Embedding 量化等策略的压缩方案,在保持模型精度不降的情况下,可将模型体积减小为原来的 7.8%,达到 5.4 MB,内存占用也随之大幅减小。再经过 [⚡️FastDeploy](https://github.com/PaddlePaddle/FastDeploy) 部署工具和 [FastTokenizer](../../fast_tokenizer/README.md) 对分词阶段的加速,**端到端推理性能**也有显著提升,从而将 ERNIE 3.0 Tiny 模型成功部署至 **📱端侧**。由于端侧部署对内存占用的要求比服务端更高,因此该方案也同样适用于 🖥服务端部署。 +- **端上语义理解压缩方案** 在语义理解任务中使用 ERNIE 3.0 Tiny 微调的基础上,我们建议进一步使用包含模型裁剪、量化训练、Embedding 量化等策略的压缩方案,在保持模型精度不降的情况下,可将模型体积减小为原来的 7.8%,达到 5.4 MB,内存占用也随之大幅减小。再经过 [⚡️FastDeploy](https://github.com/PaddlePaddle/FastDeploy) 部署工具,**端到端推理性能**也有显著提升,从而将 ERNIE 3.0 Tiny 模型成功部署至 **📱端侧**。由于端侧部署对内存占用的要求比服务端更高,因此该方案也同样适用于 🖥服务端部署。 @@ -609,7 +609,7 @@ python run_train.py \ ## ⚡️FastDeplopy 部署 -能够将深度学习模型部署到性能较低的端侧本身是比较困难的工作,因此在前面我们对小模型做了大量的优化,在精度不降的情况下将 69 MB 的模型压缩至 5.4 MB,但是如果想更好地满足业务上线要求,还需要有部署工具对性能有更多优化。在这里,PaddlePadde 提供了易用高效的云边端推理部署工具 ⚡️FastDeploy,它的 [Paddle Lite](https://github.com/PaddlePaddle/Paddle-Lite) 后端基于算子融合和常量折叠进行了深度模型优化,使得模型推理速度可有大幅度提升;它所集成的 FastTokenizer 库能够对分词阶段进行加速,在麒麟 985 芯片上单条文本的分词的推理时延低于 0.1 毫秒; +能够将深度学习模型部署到性能较低的端侧本身是比较困难的工作,因此在前面我们对小模型做了大量的优化,在精度不降的情况下将 69 MB 的模型压缩至 5.4 MB,但是如果想更好地满足业务上线要求,还需要有部署工具对性能有更多优化。在这里,PaddlePadde 提供了易用高效的云边端推理部署工具 ⚡️FastDeploy,它的 [Paddle Lite](https://github.com/PaddlePaddle/Paddle-Lite) 后端基于算子融合和常量折叠进行了深度模型优化,使得模型推理速度可有大幅度提升;在麒麟 985 芯片上单条文本的分词的推理时延低于 0.1 毫秒; 因此,本项目基于 FastDeploy 部署工具,完成了 ERNIE 3.0 Tiny 端侧和服务端的高效部署,请参考 [ERNIE 3.0 Tiny 部署文档](deploy/README.md)。以下动图是 ERNIE 3.0 Tiny 意图识别、槽位填充联合模型使用 FastDeploy 部署在 Android App 上推理的效果展示: @@ -618,7 +618,6 @@ python run_train.py \

想要更多了解 FastDeploy 可参考 [FastDeploy 仓库](https://github.com/PaddlePaddle/FastDeploy)。FastDeploy 是一款全场景、易用灵活、极致高效的 AI 推理部署工具,提供开箱即用的部署体验。它为 NLP 任务提供了一整套完整的部署 Pipeline,提供 ERNIE 3.0 Tiny 模型从文本预处理、推理引擎 Runtime 以及后处理三个阶段所需要的接口模块,开发者可以基于这些接口模块在云、边、端上部署各类常见的 NLP 任务,如文本分类、序列标注、信息抽取等: -- 在文本预处理阶段,FastDeploy 使用 PaddleNLP 提供的简单易用的高效分词工具 [FastTokenizer](../../fast_tokenizer/README.md) 完成文本预处理,开发者只需调用几行代码就能完成分词阶段开发。在麒麟 985 芯片上单条文本的分词延时低于 0.1 毫秒,将本项目模型部署在 GPU 上时,使用 FastTokenizer 工具可使端到端性能提升 **3.56倍**; - 在 Runtime 阶段,FastDeploy 集成多款硬件以及推理引擎后端,开发者可以设置 `fastdeploy::RuntimeOption` 以完成在不同硬件以及使用不同的推理引擎进行部署。目前,FastDeploy 支持的后端引擎有: - 端侧: `Paddle Lite`; - 服务端 GPU: `Paddle Inference`、`ONNX Runtime`、`Paddle TensorRT` 以及 `TensorRT`; @@ -629,7 +628,7 @@ python run_train.py \ ### 性能结论 -使用 FastDeploy 将压缩后的模型部署在华为 nova 7 Pro (麒麟 985 芯片)上,选用 Paddle Lite 作为后端进行测试,得到不同推理精度下的模型效果、端到端时延(包括前后处理)、内存占用(包括加载 FastTokenizer 库)的数据如下: +使用 FastDeploy 将压缩后的模型部署在华为 nova 7 Pro (麒麟 985 芯片)上,选用 Paddle Lite 作为后端进行测试,得到不同推理精度下的模型效果、端到端时延(包括前后处理)、内存占用的数据如下: | 模型 | 模型精度(acc.) | 推理精度 | 端到端时延(ms) | 内存占用 Pss (MB) | 模型体积(MB) | |-----------------------------------|--------------|-----------|-------------|----------------|--------------| diff --git a/model_zoo/ernie-3.0-tiny/deploy/README.md b/model_zoo/ernie-3.0-tiny/deploy/README.md index 0f83e2cbd791..4da0c09c7c0c 100644 --- a/model_zoo/ernie-3.0-tiny/deploy/README.md +++ b/model_zoo/ernie-3.0-tiny/deploy/README.md @@ -14,8 +14,6 @@ 目前 ERNIE 3.0 Tiny 模型已提供基于 FastDeploy 的云边端的部署示例,在服务端上的 GPU 硬件上,支持`Paddle Inference`、`ONNX Runtime`、`Paddle TensorRT`以及`TensorRT`后端,在CPU上支持`Paddle Inference`、`ONNX Runtime`以及`OpenVINO`后端;在移动端上支持`Paddle Lite`后端。多硬件、多推理引擎后端的支持可以满足开发者不同的部署需求。 -为了提供 ERNIE 3.0 Tiny 高性能端到端部署能力,我们使用 [FastTokenizer](../../../fast_tokenizer/README.md) 工具完成高效分词,大大提升端到端预测性能。针对 ERNIE 3.0 Tiny 模型,FastTokenizer 集成了Google提出的 [Fast WordPiece Tokenization](https://arxiv.org/pdf/2012.15524.pdf) 快速分词算法,可大幅提升分词效率。在 ERNIE 3.0 Tiny 性能测试中,使用 FastTokenizer 可提升端到端性能 **3.56倍** 。我们在 [Python部署](python/README.md) 文档更全面地展示使用 FastTokenizer 的加速能力。 - 本部署示例是车载语音场景下的口语理解(Spoken Language Understanding,SLU)任务,详细可看[ERNIE 3.0 Tiny介绍](../README.md)。 diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/.gitignore b/model_zoo/ernie-3.0-tiny/deploy/android/.gitignore deleted file mode 100644 index a5bc2e92563c..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -.DS_Store -.idea -.gradle -.cxx -cache -build -app/cache -app/libs/* -app/.cxx -app/build -app/src/main/assets/models/* -app/.gradle -app/.idea -ernie_tiny/cache -ernie_tiny/libs/fastdeploy* -ernie_tiny/.cxx -ernie_tiny/build -ernie_tiny/src/main/assets/models/* -ernie_tiny/.gradle -ernie_tiny/.idea -ui/build -ui/.gradle -ui/cache \ No newline at end of file diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/README.md b/model_zoo/ernie-3.0-tiny/deploy/android/README.md deleted file mode 100644 index 357464fa7739..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/README.md +++ /dev/null @@ -1,260 +0,0 @@ -# FastDeploy ERNIE 3.0 Tiny 模型Android部署示例 - -本目录提供了快速完成在 Android 的车载语音场景下的口语理解(Spoken Language Understanding,SLU)任务的部署示例。 - -## 环境准备 - -1. 在本地环境安装好 Android Studio 工具,详细安装方法请见[Android Stuido 官网](https://developer.android.com/studio)。 -2. 准备一部 Android 手机,并开启 USB 调试模式。开启方法: `手机设置 -> 查找开发者选项 -> 打开开发者选项和 USB 调试模式` - -## App示例编译和使用步骤 - -1. ERNIE 3.0 Tiny Android 部署示例位于`PaddleNLP/model_zoo/ernie-tiny/deploy/android`目录 -2. 用 Android Studio 打开 ernie-tiny/deploy/android 工程 -3. 手机连接电脑,打开 USB 调试和文件传输模式,并在 Android Studio 上连接自己的手机设备(手机需要开启允许从 USB 安装软件权限) - -image - - -工程内容说明: -```bash -. -├── README.md -├── app # App示例代码 -├── build.gradle -├── ernie_tiny # ERNIE Tiny JNI & Java封装代码 -# ... # 一些和gradle相关的工程配置文件 -├── local.properties -└── ui # 一些辅助用的UI代码 -``` - -> **注意:** ->> 如果您在导入项目、编译或者运行过程中遇到 NDK 配置错误的提示,请打开 ` File > Project Structure > SDK Location`,修改 `Andriod NDK location` 为您本机配置的 NDK 所在路径。本工程默认使用的NDK版本为20. ->> 如果您是通过 Android Studio 的 SDK Tools 下载的 NDK (见本章节"环境准备"),可以直接点击下拉框选择默认路径。 ->> 还有一种 NDK 配置方法,你可以在 `local.properties` 文件中手动完成 NDK 路径配置,如下图所示 ->> 如果以上步骤仍旧无法解决 NDK 配置错误,请尝试根据 Android Studio 官方文档中的[更新 Android Gradle 插件](https://developer.android.com/studio/releases/gradle-plugin?hl=zh-cn#updating-plugin)章节,尝试更新Android Gradle plugin版本。 - - -4. 点击 Run 按钮,自动编译 APP 并安装到手机。(该过程会自动下载预编译的 FastDeploy Android 库 以及 模型文件,需要联网) - 成功后效果如下,图一:APP 安装到手机;图二: APP 打开后的效果,输入文本后点击"开始分析意图"后即会自动进行意图识别和槽位分析;图三:APP 设置选项,点击右上角的设置图片,可以设置不同选项进行体验。 - -| APP 效果 | APP 演示 | APP设置项 | - | --- | --- | --- | -| image | image | image | - -如果您使用的是较旧版本的Android Studio时遇到gradle版本兼容的问题,可以修改[build.gradle](./build.gradle) 中的gradle版本号为您当前使用的版本。 -```gradle -buildscript { - // ... - dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' // 修改为您当前使用的版本 - } -} -``` - -5. 点击 APP 右上角的设置选项,可以跳转到设置页。在设置页,您可以选择不同的模型和不同的推理精度,即是否开启 FP16 和 Int8 推理,两种推理精度只能二选一。所有模型均支持FP32推理,当设置项中的 FP16 和 Int8 选项都为 false 或 不设置 时,即使用了FP32进行推理。各模型FP16和Int8推理的支持情况为: - -|模型选项|模型名称|FP16|Int8| -|---|---|---|---| -|models/ernie-tiny|原模型|✅|-| -|models/ernie-tiny-clip|原模型+裁剪(词表+模型宽度)|✅|-| -|models/ernie-tiny-clip-qat|原模型+裁剪(词表+模型宽度)+量化(矩阵乘)|-|✅| -|models/ernie-tiny-clip-qat-embedding-int8|原模型+裁剪(词表+模型宽度)+量化(矩阵乘+Embedding)|-|✅| - - -## ERNIE Tiny Java SDK 说明和使用 - -本工程除了可以直接编译 App 体验之外,还可以编译 ERNIE 3.0 Tiny 的`Java SDK`,方便用户开箱即用。 如下图所示,编译 Java SDK 的步骤为: - - 先在 Android Studio 中打开`ernie_tiny/build.gradle`工程文件; - - 选择 Build->Make Module 'android:ernietiny'; - - 从`ernie_tiny/build/outputs/aar`目录中获取编译后得到的 SDK,即`ernie_tiny-debug.aar`. - -image - -在获取到`ernie_tiny-debug.aar`后,可将其拷贝到您自己的工程中进行使用。在 Android Studio 中的配置步骤如下: -(1)首先,将`ernie_tiny-debug.aar`拷贝到您 Android 工程的libs目录下。 -```bash -├── build.gradle -├── libs -│   └── ernie_tiny-debug.aar -├── proguard-rules.pro -└── src -``` -(2)在您的 Android 工程中的 build.gradle 引入 ERNIE 3.0 Tiny SDK,如下 -```groovy -dependencies { - implementation fileTree(include: ['ernie_tiny-debug.aar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:28.0.0' - // ... -} -``` - -### ERNIE 3.0 Tiny Java API说明如下 - -- ERNIE 3.0 Tiny `Predictor` 初始化 API: 模型初始化 API 包含两种方式,方式一是通过构造函数直接初始化;方式二是,通过调用 init 函数,在合适的程序节点进行初始化。ERNIE 3.0 Tiny Predictor 初始化参数说明如下: - - modelFile: String, paddle 格式的模型文件路径,如 infer_model.pdmodel - - paramFile: String, paddle 格式的参数文件路径,如 infer_model.pdiparams - - vocabFile: String, 词表文件,如 vocab.txt 每一行包含一个词 - - slotLabelsFile: String, 槽位标签文件,如 slots_label.txt - - intentLabelsFile: String, 意图标签文件,如 intent_label.txt 每一行包含一个标签 - - addedTokensFile: String, 额外词表文件,如 added_tokens.json,json文件 - - runtimeOption: RuntimeOption,可选参数,模型初始化 option。如果不传入该参数则会使用默认的运行时选项。 - - maxLength: 最大序列长度,默认为16 - -```java -public Predictor(); // 空构造函数,之后可以调用init初始化 -public Predictor(String modelFile, String paramsFile, String vocabFile, String slotLabelsFile, String intentLabelsFile, String addedTokensFile); -public Predictor(String modelFile, String paramsFile, String vocabFile, String slotLabelsFile, String intentLabelsFile, String addedTokensFile, RuntimeOption runtimeOption, int maxLength); -public boolean init(String modelFile, String paramsFile, String vocabFile, String slotLabelsFile, String intentLabelsFile, String addedTokensFile, RuntimeOption runtimeOption, int maxLength); -``` - -- ERNIE 3.0 Tiny `Predictor` 预测 API:Predictor 提供 predict 接口对输出的文本进行意图识别。 -```java -public IntentDetAndSlotFillResult[] predict(String[] texts); -``` - -- ERNIE 3.0 Tiny Predictor 资源释放 API: 调用 release() API 可以释放模型资源,返回 true 表示释放成功,false 表示失败;调用 initialized() 可以判断 Predictor 是否初始化成功,true 表示初始化成功,false 表示失败。 -```java -public boolean release(); // 释放native资源 -public boolean initialized(); // 检查Predictor是否初始化成功 -``` - -- RuntimeOption设置说明 - -```java -public class RuntimeOption { - public void enableLiteFp16(); // 开启fp16精度推理 - public void disableLiteFP16(); // 关闭fp16精度推理(默认关闭) - public void enableLiteInt8(); // 开启int8精度推理(需要先准备好量化模型) - public void disableLiteInt8(); // 关闭int8精度推理(默认关闭) - public void setCpuThreadNum(int threadNum); // 设置线程数 - public void setLitePowerMode(LitePowerMode mode); // 设置能耗模式 - public void setLitePowerMode(String modeStr); // 通过字符串形式设置能耗模式 -} -``` - -- 意图和槽位识别结果`IntentDetAndSlotFillResult`说明 - -```java -public class IntentDetAndSlotFillResult { - public String mStr; // 可用于debug的字符串 拼接了意图识别和槽位识别的结果 - public boolean mInitialized = false; - public IntentDetResult mIntentResult; // 意图识别结果 - public SlotFillResult[] mSlotResult; // 槽位识别结果 - - static class IntentDetResult { - public String mIntentLabel; // 意图识别结果文本标签 - public float mIntentConfidence; // 意图识别结果置信度 - } - static class SlotFillResult { - public String mSlotLabel; // 槽位识别结果文本标签 - public String mEntity; // 槽位识别的实体 - public int[] mPos; // [2] // 在原始文本对应的位置 [start,end] - } -} -``` - -- ERNIE 3.0 Tiny `Predictor` FP32/FP16 推理示例 - -```java -import com.baidu.paddle.paddlenlp.ernie_tiny.RuntimeOption; -import com.baidu.paddle.paddlenlp.ernie_tiny.Predictor; -import com.baidu.paddle.paddlenlp.ernie_tiny.IntentDetAndSlotFillResult; -import android.app.Activity; - -// 以下为伪代码 -class TestERNIETiny extends Activity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Predictor predictor = new Predictor(); - - // 设置模型文件和标签文件 - String modelFile = "ernie-tiny/infer_model.pdmodel"; - String paramsFile = "ernie-tiny/infer_model.pdiparams"; - String vocabFile = "ernie-tiny/vocab.txt"; - String slotLabelsFile = "ernie-tiny/slots_label.txt"; - String intentLabelsFile = "ernie-tiny/intent_label.txt"; - String addedTokensFile = "ernie-tiny/added_tokens.json"; - - // RuntimeOption 设置 - RuntimeOption option = new RuntimeOption(); - option.setCpuThreadNum(2); // 设置线程数 - option.enableLiteFp16(); // 是否开启FP16精度推理 - - // Predictor初始化 - predictor.init(modelFile, paramsFile, vocabFile, slotLabelsFile, intentLabelsFile, addedTokensFile, option, 16); - - // 进行意图识别和槽位分析 - String[] inputTexts = new String[]{"来一首周华健的花心", "播放我们都一样", "到信阳市汽车配件城"}; - - IntentDetAndSlotFillResult[] results = predictor.predict(inputTexts); - } -} -``` - -- ERNIE 3.0 Tiny `Predictor` Int8 量化模型推理示例 - -```java -import com.baidu.paddle.paddlenlp.ernie_tiny.RuntimeOption; -import com.baidu.paddle.paddlenlp.ernie_tiny.Predictor; -import com.baidu.paddle.paddlenlp.ernie_tiny.IntentDetAndSlotFillResult; -import android.app.Activity; - -// 以下为伪代码 -class TestERNIETiny extends Activity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Predictor predictor = new Predictor(); - - // 设置模型文件和标签文件 - String modelFile = "ernie-tiny-clip-qat-embedding-int8/infer_model.pdmodel"; - String paramsFile = "ernie-tiny-clip-qat-embedding-int8/infer_model.pdiparams"; - String vocabFile = "ernie-tiny-clip-qat-embedding-int8/vocab.txt"; - String slotLabelsFile = "ernie-tiny-clip-qat-embedding-int8/slots_label.txt"; - String intentLabelsFile = "ernie-tiny-clip-qat-embedding-int8/intent_label.txt"; - String addedTokensFile = "ernie-tiny-clip-qat-embedding-int8/added_tokens.json"; - - // RuntimeOption 设置 - RuntimeOption option = new RuntimeOption(); - option.setCpuThreadNum(2); // 设置线程数 - option.enableLiteInt8(); // 开启int8精度推理(需要先准备好量化模型) - - // Predictor初始化 - predictor.init(modelFile, paramsFile, vocabFile, slotLabelsFile, intentLabelsFile, addedTokensFile, option, 16); - - // 进行意图识别和槽位分析 - String[] inputTexts = new String[]{"来一首周华健的花心", "播放我们都一样", "到信阳市汽车配件城"}; - - IntentDetAndSlotFillResult[] results = predictor.predict(inputTexts); - } -} -``` - -更详细的用法请参考 [ERNIETinyMainActivity](./app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyMainActivity.java) 中的用法 - -## 替换 App 示例中的 ERNIE 3.0 Tiny 模型 - -替换 App 示例中的模型的步骤非常简单,模型所在的位置为 `app/src/main/assets/models`。替换模型之前请确保您的模型目录中包含 vocab.txt、slots_label.txt、intent_label.txt 以及 added_token.json 等意图识别和槽位分析所需要的词表和标签文件。替换模型的步骤为: - - 将您的 ERNIE 3.0 Tiny 模型放在 `app/src/main/assets/models` 目录下; - - 修改 `app/src/main/res/values/strings.xml` 中模型路径的默认值,如: - -```xml - -models/ernie-tiny-clip-qat-embedding-int8 -``` - -## 关于 ERNIE 3.0 Tiny JNI 的封装 - -如果您对 ERNIE 3.0 Tiny JNI 封装的实现感兴趣,可以参考 [ernie_tiny_jni/predictor_jni.cc](./ernie_tiny/src/main/cpp/ernie_tiny_jni/predictor_jni.cc), 关于如何使用 JNI 进行模型封装并和 Java 通信,可以参考 [FastDeploy/use_cpp_sdk_on_android.md](https://github.com/PaddlePaddle/FastDeploy/blob/develop/docs/cn/faq/use_cpp_sdk_on_android.md) 文档中的说明。 - -## 相关文档 - -[ERNIE 3.0 Tiny 模型详细介绍](../../README.md) - -[ERNIE 3.0 Tiny 模型C++部署方法](../cpp/README.md) - -[ERNIE 3.0 Tiny 模型Python部署方法](../python/README.md) - -[FastDeploy SDK 安装文档](https://github.com/PaddlePaddle/FastDeploy/blob/develop/docs/cn/build_and_install/download_prebuilt_libraries.md) \ No newline at end of file diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/build.gradle b/model_zoo/ernie-3.0-tiny/deploy/android/app/build.gradle deleted file mode 100644 index 2037c7560959..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/build.gradle +++ /dev/null @@ -1,91 +0,0 @@ -apply plugin: 'com.android.application' - -android { - compileSdk 28 - - defaultConfig { - applicationId 'com.baidu.paddle.paddlenlp.app' - minSdkVersion 15 - //noinspection ExpiredTargetSdkVersion - targetSdkVersion 28 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - implementation project(path: ':ernie_tiny') - implementation project(path: ':ui') - //noinspection GradleCompatible - implementation 'com.android.support:appcompat-v7:28.0.0' - //noinspection GradleDependency - implementation 'com.android.support.constraint:constraint-layout:1.1.3' - implementation 'com.android.support:design:28.0.0' - implementation 'org.jetbrains:annotations:15.0' - //noinspection GradleDependency - testImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' -} - -def FD_MODEL = [ - [ - 'src' : 'https://bj.bcebos.com/paddlehub/fastdeploy/ernie-tiny.tgz', - 'dest': 'src/main/assets/models' - ], - [ - 'src' : 'https://bj.bcebos.com/paddlehub/fastdeploy/ernie-tiny-clip.tgz', - 'dest': 'src/main/assets/models' - ], - [ - 'src' : 'https://bj.bcebos.com/paddlehub/fastdeploy/ernie-tiny-clip-qat.tgz', - 'dest': 'src/main/assets/models' - ], - [ - 'src' : 'https://bj.bcebos.com/paddlehub/fastdeploy/ernie-tiny-clip-qat-embedding-int8.tgz', - 'dest': 'src/main/assets/models' - ] - -] - -task downloadAndExtractModels(type: DefaultTask) { - doFirst { - println "[INFO] Downloading and extracting models ..." - } - doLast { - String cachePath = "cache" - if (!file("${cachePath}").exists()) { - mkdir "${cachePath}" - } - FD_MODEL.eachWithIndex { model, index -> - String[] modelPaths = model.src.split("/") - String modelName = modelPaths[modelPaths.length - 1] - String modelPrefix = modelName.substring(0, modelName.length() - 4) - boolean copyFiles = false - if (!file("${model.dest}/${modelPrefix}").exists()) { - // Download the target model if not exists - if (!file("${cachePath}/${modelName}").exists()) { - println "[INFO] Downloading ${model.src} -> ${cachePath}/${modelName}" - ant.get(src: model.src, dest: file("${cachePath}/${modelName}")) - } - copyFiles = true - } - if (copyFiles) { - println "[INFO] Taring ${cachePath}/${modelName} -> ${model.dest}/${modelPrefix}" - copy { from(tarTree("${cachePath}/${modelName}")) into("${model.dest}") } - } else { - println "[INFO] ${model.dest}/${modelPrefix} already exists!" - } - } - } -} - -preBuild.dependsOn downloadAndExtractModels diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/proguard-rules.pro b/model_zoo/ernie-3.0-tiny/deploy/android/app/proguard-rules.pro deleted file mode 100644 index 481bb4348141..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/AndroidManifest.xml b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 08851e680b01..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyMainActivity.java b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyMainActivity.java deleted file mode 100644 index 3fb229bec15f..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyMainActivity.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.baidu.paddle.paddlenlp.app.ernie_tiny; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ImageView; - -import com.baidu.paddle.paddlenlp.app.R; -import com.baidu.paddle.paddlenlp.ernie_tiny.RuntimeOption; -import com.baidu.paddle.paddlenlp.ernie_tiny.Predictor; -import com.baidu.paddle.paddlenlp.ernie_tiny.IntentDetAndSlotFillResult; -import com.baidu.paddle.paddlenlp.ui.Utils; - - -public class ERNIETinyMainActivity extends Activity implements View.OnClickListener { - private static final String TAG = ERNIETinyMainActivity.class.getSimpleName(); - private ImageView back; - private ImageButton btnSettings; - private EditText etERNIETinyInput; - private EditText etERNIETinyOutput; - private Button btnERNIETinyAnalysis; - private String[] inputTexts; - - // Call 'init' and 'release' manually later - Predictor predictor = new Predictor(); - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.ernie_tiny_activity_main); - - // Clear all setting items to avoid app crashing due to the incorrect settings - initSettings(); - - // Check and request WRITE_EXTERNAL_STORAGE permissions - if (!checkAllPermissions()) { - requestAllPermissions(); - } - - // Init the camera preview and UI components - initView(); - } - - @Override - protected void onResume() { - super.onResume(); - // Reload settings and re-initialize the predictor - checkAndUpdateSettings(); - } - - @Override - protected void onPause() { - super.onPause(); - } - - @Override - protected void onDestroy() { - if (predictor != null) { - predictor.release(); - } - super.onDestroy(); - } - - private void initView() { - // Back from setting page to main page - back = findViewById(R.id.iv_back); - back.setOnClickListener(this); - // Apply ERNIE Tiny predict - btnERNIETinyAnalysis = findViewById(R.id.btn_ernie_tiny_analysis); - btnERNIETinyAnalysis.setOnClickListener(this); - // ERNIE Tiny input and output texts - etERNIETinyInput = findViewById(R.id.et_ernie_tiny_input); - etERNIETinyOutput = findViewById(R.id.et_ernie_tiny_output); - // Setting page - btnSettings = findViewById(R.id.btn_settings); - btnSettings.setOnClickListener(this); - } - - @SuppressLint("NonConstantResourceId") - @Override - public void onClick(View view) { - switch (view.getId()) { - case R.id.btn_settings: - startActivity(new Intent(ERNIETinyMainActivity.this, ERNIETinySettingsActivity.class)); - break; - case R.id.iv_back: - finish(); - break; - case R.id.btn_ernie_tiny_analysis: - extractTextsIntentAndSlot(); - break; - default: - break; - } - - } - - public void extractTextsIntentAndSlot() { - if (updateInputTexts()) { - IntentDetAndSlotFillResult[] results = predictor.predict(inputTexts); - updateOutputTexts(results); - } - } - - public void updateOutputTexts(IntentDetAndSlotFillResult[] results) { - if (results == null) { - etERNIETinyOutput.setText("分析结果为空"); - return; - } - if (inputTexts == null) { - etERNIETinyOutput.setText("输入文本为空"); - return; - } - if (inputTexts.length != results.length) { - String info = "输入文本数量与分析结果数量不一致!" - + inputTexts.length + "!=" + results.length; - etERNIETinyOutput.setText(info); - return; - } - // Merge Result Str - StringBuilder resultStrBuffer = new StringBuilder(); - for (int i = 0; i < results.length; ++i) { - resultStrBuffer - .append("NO.") - .append(i) - .append(" text = ") - .append(inputTexts[i]) - .append("\n") - .append(results[i].mStr) - .append("\n"); - } - // Update output text view (EditText) - etERNIETinyOutput.setText(resultStrBuffer.toString()); - } - - public boolean updateInputTexts() { - String combinedInputText = etERNIETinyInput.getText().toString(); - if (combinedInputText == null || combinedInputText.length() == 0) { - // Use default text if no custom text - combinedInputText = getString(R.string.ERNIE_TINY_INPUT_TEXTS_DEFAULT); - } - String[] texts = combinedInputText.split("[。!!:;:;]"); - if (texts.length <= 0) { - return false; - } - for (int i = 0; i < texts.length; ++i) { - texts[i] = texts[i].trim(); - } - // Update input texts - inputTexts = texts; - return true; - } - - @SuppressLint("ApplySharedPref") - public void initSettings() { - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.clear(); - editor.commit(); - ERNIETinySettingsActivity.resetSettings(); - } - - public void checkAndUpdateSettings() { - if (ERNIETinySettingsActivity.checkAndUpdateSettings(this)) { - // Clear output text first - etERNIETinyOutput.setText(""); - - // Update predictor - String realModelDir = getCacheDir() + "/" + ERNIETinySettingsActivity.modelDir; - Utils.copyDirectoryFromAssets(this, ERNIETinySettingsActivity.modelDir, realModelDir); - - String modelFile = realModelDir + "/" + "infer_model.pdmodel"; - String paramsFile = realModelDir + "/" + "infer_model.pdiparams"; - String vocabFile = realModelDir + "/" + "vocab.txt"; - String slotLabelsFile = realModelDir + "/" + "slots_label.txt"; - String intentLabelsFile = realModelDir + "/" + "intent_label.txt"; - String addedTokensFile = realModelDir + "/" + "added_tokens.json"; - RuntimeOption option = new RuntimeOption(); - option.setCpuThreadNum(ERNIETinySettingsActivity.cpuThreadNum); - option.setLitePowerMode(ERNIETinySettingsActivity.cpuPowerMode); - if (Boolean.parseBoolean(ERNIETinySettingsActivity.enableLiteInt8)) { - option.enableLiteInt8(); // For quantized models - } else { - // Enable FP16 if Int8 option is not ON. - if (Boolean.parseBoolean(ERNIETinySettingsActivity.enableLiteFp16)) { - option.enableLiteFp16(); - } - } - predictor.init(modelFile, paramsFile, vocabFile, slotLabelsFile, - intentLabelsFile, addedTokensFile, option, 16); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) { - new AlertDialog.Builder(ERNIETinyMainActivity.this) - .setTitle("Permission denied") - .setMessage("Click to force quit the app, then open Settings->Apps & notifications->Target " + - "App->Permissions to grant all of the permissions.") - .setCancelable(false) - .setPositiveButton("Exit", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ERNIETinyMainActivity.this.finish(); - } - }).show(); - } - } - - private void requestAllPermissions() { - ActivityCompat.requestPermissions( - this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - 0); - } - - private boolean checkAllPermissions() { - return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED; - } - -} diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinySettingsActivity.java b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinySettingsActivity.java deleted file mode 100644 index 3e4c7cc394fd..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinySettingsActivity.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.baidu.paddle.paddlenlp.app.ernie_tiny; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.ListPreference; -import android.preference.PreferenceManager; -import android.support.v7.app.ActionBar; - -import com.baidu.paddle.paddlenlp.app.R; -import com.baidu.paddle.paddlenlp.ui.view.AppCompatPreferenceActivity; - - -public class ERNIETinySettingsActivity extends AppCompatPreferenceActivity implements - SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = ERNIETinySettingsActivity.class.getSimpleName(); - static public String modelDir = ""; - static public int cpuThreadNum = 2; - static public String cpuPowerMode = ""; - static public String enableLiteFp16 = "false"; - static public String enableLiteInt8 = "true"; - - ListPreference lpChoosePreInstalledModel = null; - ListPreference lpCPUThreadNum = null; - ListPreference lpCPUPowerMode = null; - ListPreference lpEnableLiteFp16 = null; - ListPreference lpEnableLiteInt8 = null; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.ernie_tiny_settings); - ActionBar supportActionBar = getSupportActionBar(); - if (supportActionBar != null) { - supportActionBar.setDisplayHomeAsUpEnabled(true); - } - - // Setup UI components - lpChoosePreInstalledModel = - (ListPreference) findPreference(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY)); - lpCPUThreadNum = (ListPreference) findPreference(getString(R.string.CPU_THREAD_NUM_KEY)); - lpCPUPowerMode = (ListPreference) findPreference(getString(R.string.CPU_POWER_MODE_KEY)); - lpEnableLiteFp16 = (ListPreference) findPreference(getString(R.string.ENABLE_LITE_FP16_MODE_KEY)); - lpEnableLiteInt8 = (ListPreference) findPreference(getString(R.string.ENABLE_LITE_INT8_MODE_KEY)); - } - - @SuppressLint("ApplySharedPref") - private void reloadSettingsAndUpdateUI() { - SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); - - String model_dir = sharedPreferences.getString(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY), - getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_DEFAULT)); - - String cpu_thread_num = sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), - getString(R.string.CPU_THREAD_NUM_DEFAULT)); - String cpu_power_mode = sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), - getString(R.string.CPU_POWER_MODE_DEFAULT)); - String enable_lite_fp16 = sharedPreferences.getString(getString(R.string.ENABLE_LITE_FP16_MODE_KEY), - getString(R.string.ENABLE_LITE_FP16_MODE_DEFAULT)); - String enable_lite_int8 = sharedPreferences.getString(getString(R.string.ENABLE_LITE_INT8_MODE_KEY), - getString(R.string.ENABLE_LITE_FP16_MODE_DEFAULT)); - - lpChoosePreInstalledModel.setSummary(model_dir); - lpChoosePreInstalledModel.setValue(model_dir); - lpCPUThreadNum.setValue(cpu_thread_num); - lpCPUThreadNum.setSummary(cpu_thread_num); - lpCPUPowerMode.setValue(cpu_power_mode); - lpCPUPowerMode.setSummary(cpu_power_mode); - lpEnableLiteFp16.setValue(enable_lite_fp16); - lpEnableLiteFp16.setSummary(enable_lite_fp16); - lpEnableLiteInt8.setValue(enable_lite_int8); - lpEnableLiteInt8.setSummary(enable_lite_int8); - } - - static boolean checkAndUpdateSettings(Context ctx) { - boolean settingsChanged = false; - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx); - - String model_dir = sharedPreferences.getString(ctx.getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY), - ctx.getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_DEFAULT)); - settingsChanged |= !modelDir.equalsIgnoreCase(model_dir); - modelDir = model_dir; - - String cpu_thread_num = sharedPreferences.getString(ctx.getString(R.string.CPU_THREAD_NUM_KEY), - ctx.getString(R.string.CPU_THREAD_NUM_DEFAULT)); - settingsChanged |= cpuThreadNum != Integer.parseInt(cpu_thread_num); - cpuThreadNum = Integer.parseInt(cpu_thread_num); - - String cpu_power_mode = sharedPreferences.getString(ctx.getString(R.string.CPU_POWER_MODE_KEY), - ctx.getString(R.string.CPU_POWER_MODE_DEFAULT)); - settingsChanged |= !cpuPowerMode.equalsIgnoreCase(cpu_power_mode); - cpuPowerMode = cpu_power_mode; - - String enable_lite_fp16 = sharedPreferences.getString(ctx.getString(R.string.ENABLE_LITE_FP16_MODE_KEY), - ctx.getString(R.string.ENABLE_LITE_FP16_MODE_DEFAULT)); - settingsChanged |= !enableLiteFp16.equalsIgnoreCase(enable_lite_fp16); - enableLiteFp16 = enable_lite_fp16; - - String enable_lite_int8 = sharedPreferences.getString(ctx.getString(R.string.ENABLE_LITE_INT8_MODE_KEY), - ctx.getString(R.string.ENABLE_LITE_INT8_MODE_DEFAULT)); - settingsChanged |= !enableLiteInt8.equalsIgnoreCase(enable_lite_int8); - enableLiteInt8 = enable_lite_int8; - - return settingsChanged; - } - - static void resetSettings() { - modelDir = ""; - cpuThreadNum = 2; - cpuPowerMode = ""; - enableLiteFp16 = "false"; - enableLiteInt8 = "true"; - } - - @Override - protected void onResume() { - super.onResume(); - getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); - reloadSettingsAndUpdateUI(); - } - - @Override - protected void onPause() { - super.onPause(); - getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - reloadSettingsAndUpdateUI(); - } -} diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyWelcomeActivity.java b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyWelcomeActivity.java deleted file mode 100644 index 9d9aace3594c..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/java/com/baidu/paddle/paddlenlp/app/ernie_tiny/ERNIETinyWelcomeActivity.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.baidu.paddle.paddlenlp.app.ernie_tiny; - -import android.app.Activity; -import android.content.Intent; -import android.graphics.Color; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.view.View; - -import com.baidu.paddle.paddlenlp.app.R; - -public class ERNIETinyWelcomeActivity extends Activity { - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { - getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - ); - getWindow().setStatusBarColor(Color.TRANSPARENT); - } - setContentView(R.layout.ernie_tiny_welcome); - } - - public void startActivity(View view) { - Intent intent = new Intent(ERNIETinyWelcomeActivity.this, ERNIETinyMainActivity.class); - startActivity(intent); - } -} diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 1f6bb290603d..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-v24/round_corner_btn.xml b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-v24/round_corner_btn.xml deleted file mode 100644 index c5dcc45d5637..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-v24/round_corner_btn.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - \ No newline at end of file diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-xhdpi/back_btn.png b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-xhdpi/back_btn.png deleted file mode 100644 index ff121e85f5614dfd022f39627028af825a46d683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 455 zcmV;&0XY7NP)Px$fk{L`R7ee_)juf2aTv$(yX$hI{E2}ivam=p$Rx_3WKfbxN~EM@k(5%BO-WfU zWl@q%HpwItU5P~`e@e=q$e-iq{oFle{yooApZi!ouPf;*5^-D*1ZCuuOqv~5> zrA!dR1lbdTyC*fFAx1H>N#tHgV`xMM43|aVK1sV3na&VF@JshukwHbI#;r&f<8Or) ztVj#Mn<8sgqz>RwksUf78e&vIt`s?>^DIaa!;~UtbcqGYWq6>-4P9kH>^Eu$f78R5Cr_+ovj*@l7te|F0k6$6}HucYmJGEE%xis4oBj002ovPDHLkV1m`y!3qEX diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-xhdpi/more_menu.png b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable-xhdpi/more_menu.png deleted file mode 100644 index edf9f3ccced5afeb71d9516d93ea19f26c7d9984..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 414 zcmV;P0b%}$P)0`;fA>{1X_Il!ymh=^7u{{Rl_|GL>6mPM;1WY$RjBH$w zWyNH^^e|SD$^D5?A`~MKi>Dn*gkl6@@w7vUP>et<-f}zi4a_uOC8db_zyJUM07*qo IM6N<$f|TdAY5)KL diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings.xml b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings.xml deleted file mode 100644 index 917897b99981..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings_default.xml b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings_default.xml deleted file mode 100644 index e19589a97e41..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings_default.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings_pressed.xml b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings_pressed.xml deleted file mode 100644 index c4af2a042de3..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/btn_settings_pressed.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/ic_launcher_background.xml b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 0d025f9bf6b6..000000000000 --- a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/main_bk.png b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/main_bk.png deleted file mode 100644 index 1ff9457d4d74c9b486c6c5094d3396aaea417d1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 594200 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⪻kceyRr?t*z2iqdt^H5=G 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⋘gX<*FN_PBoam^N9IregTL~3 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∑VT#ok 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 diff --git a/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/paddle_logo.png b/model_zoo/ernie-3.0-tiny/deploy/android/app/src/main/res/drawable/paddle_logo.png deleted file mode 100644 index bc1135abfab7aa48f29392da4bca614f688314af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5043 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 JPDHLkV1n`1 - - - - - - - - - - - - - - - -