Metadata-Version: 2.4
Name: strophe
Version: 0.1.0
Summary: A fast and type-safe Python library for NLP compatible with Stanza models
License-Expression: Apache-2.0
License-File: LICENSE
License-File: LICENSES/gigatoken.txt
License-File: LICENSES/Highway.txt
License-File: LICENSES/ICU.txt
License-File: LICENSES/nanobind.txt
License-File: NOTICE
License-File: THIRD_PARTY_NOTICES.md
Classifier: Development Status :: 5 - Production/Stable
Classifier: Programming Language :: C++
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
Classifier: Programming Language :: Python :: Implementation :: CPython
Requires-Python: >=3.10
Requires-Dist: torch<2.14,>=2.13
Provides-Extra: converter
Requires-Dist: peft<1,>=0.14; extra == "converter"
Requires-Dist: safetensors<1,>=0.4; extra == "converter"
Requires-Dist: stanza==1.14.0; extra == "converter"
Requires-Dist: transformers<6,>=4.49; extra == "converter"
Description-Content-Type: text/markdown

# Strophe

Strophe は、Stanza の観測可能な意味論との互換性を目指す C++20 製の
ネイティブ実行器です。Python API は nanobind で公開し、ビルドには
scikit-build-core と CMake、Python 環境・依存関係の管理には uv を使います。

通常の pip または uv 等でインストールできます。

```console
pip install strophe # pip
```

```console
uv add strophe # uv
```

現在は native schema、model bundle loader に加え、LangID、多言語 tokenizer、
MWT、POS、lemma、depparse、NER、sentiment、constituency、coref の native
inference に加え、MorphSeg の native inference を実装しています。
POS、depparse、NER、sentiment、constituency は
static embedding に加え ATen charLM と LibTorch Transformer profile を
扱えます。coref は LoRA adapter を変換時に基底 Transformer へ融合し、
AOTInductor と ATen の scoring head で実行します。
UTF-8 バイト列と Python の code point index の対応を保持する SoA storage
上に、次の軽量 view を実装しています。

- `Document`
- `Sentence`
- `Token`
- `Word`
- `Span`
- `ConstituencyTree`
- `CorefMention` / `CorefChain` / `CorefAttachment`

`Document.sentences`、`Sentence.tokens`、`Sentence.words`、`Token.words`、
`Span.tokens` などは Python list を毎回作るのではなく、negative index、slice、
iterator、`reversed()`、`in` に対応した native sequence view を返します。
`to_dict()` は互換境界として実際の `list[list[dict]]` を生成します。

全 processor は型付きで登録済みです。

```text
langid, tokenize, mwt, pos, lemma, depparse, ner, sentiment,
constituency, coref, morphseg
```

依存関係と提供 annotation を公開し、Pipeline 構築時に順序を検証します。
`langid`、`tokenize`、`mwt`、`pos`、`lemma`、`depparse`、`ner`、`sentiment`、
`constituency`、`coref`、`morphseg` は変換済み bundle を CPU/float32 の
ATen 演算で実行します。MorphSeg は Embedding → 2 層 BiLSTM → 線形射影 →
GELU → 線形分類 → argmax を ATen で実行し、入力語はシステム ICU の
NFKC → case fold → NFC で正規化します。
Transformer encoder は変換時に `torch.export` / AOTInductor で対象環境用の
LibTorch package にコンパイルし、C++ から Python 依存の `torch` が提供する
共有ライブラリで実行します。それ以外の processor は引き続き
`ProcessorNotImplementedError` を明示的に送出します。lemma bundle が POS を
使う場合は、Pipeline 構築時にも `pos` を必須 annotation として扱います。
公開 API を単独で実行できる小さな例は
[`examples/`](examples/README.md) にまとめています。

## データモデル

Stanza の `Document.to_dict()` と同じ flattened MWT 形式から native storage を
構築できます。

```python
import strophe

doc = strophe.Document.from_dict(
    [
        [
            {
                "id": 1,
                "text": "Stanford",
                "lemma": "Stanford",
                "upos": "PROPN",
                "head": 0,
                "deprel": "root",
                "start_char": 0,
                "end_char": 8,
                "ner": "S-ORG",
            }
        ]
    ],
    text="Stanford",
)

assert doc.sentences[0].words[0].lemma == "Stanford"
assert doc.ents[0].type == "ORG"
assert isinstance(doc.to_dict(), list)
```

processor の構成とロード状態も問い合わせられます。tokenizer/MWT を実行するには、
converter の `--output-dir` と同じ root を `model_dir` に渡します。`default`
package は `index.json` にある対象 processor の concrete package が一つなら
それを選択します。

```python
pipeline = strophe.Pipeline(
    "en",
    processors="tokenize,mwt",
    model_dir="models",
    resources_version="1.14.0",
)

doc = pipeline("I can't believe it works.")

assert pipeline.processors[0].implemented is True
assert [token.text for token in doc.sentences[0].tokens] == [
    "I",
    "can't",
    "believe",
    "it",
    "works",
    ".",
]
assert [word.text for word in doc.sentences[0].tokens[1].words] == [
    "ca",
    "n't",
]
```

processor ごとに異なる bundle package を選ぶ場合は、`download()` と同じ
processor-to-package mapping を `package` に渡せます。mapping にない processor
は `"default"` として解決されます。

```python
pipeline = strophe.Pipeline(
    "en",
    processors="tokenize,pos,ner",
    package={
        "tokenize": "combined_nocharlm",
        "pos": "combined_charlm",
        "ner": "ontonotes-ww-multi_charlm",
    },
    model_dir="models",
)
```

## 開発

Python 3.10 以降、C++20 対応コンパイラ、CMake、Python package の `torch`、
Highway、ICU が必要です。CMake は build に使う Python へ `torch` の CMake prefix
を問い合わせ、`NO_DEFAULT_PATH` でその package に限定して LibTorch を解決
します。Highway は Python と独立して runtime dispatch を行い、標準の
CMake config package として
`find_package()` で探索します。ICU の Unicode common component は
`find_package(ICU COMPONENTS uc)` の `ICU::uc` target としてシステムリンク
します。通常の CMake search path にない場所へ導入した場合は、
`STROPHE_HIGHWAY_ROOT` と `STROPHE_ICU_ROOT`、または
`CMAKE_PREFIX_PATH` を指定できます。

```console
uv build --wheel \
  -Ccmake.define.STROPHE_HIGHWAY_ROOT="/path/to/highway" \
  -Ccmake.define.STROPHE_ICU_ROOT="/path/to/icu"
```

wheel build では `STROPHE_BUNDLE_NATIVE_DEPENDENCIES=ON` が既定です。
`find_package()` が公開した Highway の imported target が共有ライブラリなら、
推移的な非システム依存も `strophe/.libs` へ収容し、
相対 runtime path から読み込みます。静的ライブラリは `_strophe` にリンク
済みなのでコピーしません。PyTorch の共有ライブラリは同梱せず、
Python 依存の `torch` package から読み込みます。OS package 用など、実行環境
の共有ライブラリを使う build では次のように無効化できます。

```console
uv build --wheel \
  -Ccmake.define.STROPHE_BUNDLE_NATIVE_DEPENDENCIES=OFF
```

第三者へ wheel を配布する場合は、実際に同梱された Highway と
その推移依存について、各ライセンスの再配布条件も満たす必要があります。
ICU は wheel へ同梱せず、実行環境のシステム ICU を使用します。

scikit-build-core の build directory は `build/` に固定しているため、clangd は
`build/compile_commands.json` をそのまま参照できます。通常の unit/E2E test
は Strophe の公開契約とリポジトリ内で生成する deterministic な tiny bundle
だけを使い、Stanza の実行環境や checkpoint を必要としません。

```console
uv sync --group dev
uv run pytest
```

converter 自体を開発するときだけ、Stanza、Transformers、PEFT
を含む converter group を追加します。

```console
uv sync --group dev --group converter
```

wheel と source distribution は次のコマンドで作成できます。

```console
uv build
```

ドキュメントは Zensical と mkdocstrings-python で生成します。

```console
uv run --group doc zensical serve
uv run --group doc zensical build --strict
```

通常の CPython 3.10〜3.14 に加え、別 ABI の free-threaded CPython 3.14t
を対象にします。共有可能な model object、`Document` の所有規則、GIL と
ATen thread pool の扱いは
[thread safety の契約](docs/thread-safety.md)を参照してください。

## ベンチマーク

`benchmarks/` に固定コーパス、Strophe／Stanza の独立 runner、`hyperfine`
比較、両実装用の Torch Profiler entry point を用意しています。例えば full
pipeline は次のように比較できます。

```console
uv run --group converter python -m benchmarks.compare \
  --model-dir models \
  --processors tokenize,mwt,pos,lemma,depparse \
  --documents 32 \
  --iterations 5 \
  --runs 10
```

変換時に保存された `models/<resources-version>/` は Stanza の model root
として再利用します。比較条件、結果のexport、Processorごとのpackage指定、
個別 profilingの詳細は [benchmark guide](benchmarks/README.md)を参照して
ください。

性能最適化は、同じ bundle、dtype、ATen 演算、演算順序、batch boundary、
argmax / top-k / Viterbi / MST の tie-break を維持する範囲に限定します。
ロード時の immutable Tensor view・LSTM state のキャッシュ、不要な
Tensor / `std::vector` 往復の除去、出力 buffer の事前確保、Unicode・subword
列の単一走査など、算術結果に触れないデータパイプライン改善を優先します。
再学習、蒸留、量子化、低精度化、近似復号、`fast-math` は性能プロファイルにも
導入しません。

## Stanza モデル変換

変換器は `stanza.download()` と同じ resource catalog の package、alias、MWT、
依存モデル解決を行います。catalog とモデルをバージョン別 cache に保存し、各
checkpoint の MD5 を検証します。Stanza の `default.zip` shortcut は使わず、
変換元を個別に追跡できるよう同じ package を個別モデルへ展開します。

Stropheのwheelとsdistには、Stanza checkpoint、language pack、word vector、
変換済みbundleを含めません。モデルは利用者の環境で取得・変換され、選択した
モデルとその学習データ固有のライセンスが引き続き適用されます。変換済みbundleは
StropheのApache-2.0ライセンスへ移行しません。

変換環境は Stanza と resource のバージョンを完全一致させます。

Python API は download と native bundle への変換を一度に行います。同じ
resource catalog と checkpoint から作成済みの検証済み bundle は再利用されます。
変換依存は `uv sync --group converter`、配布 package では
`strophe[converter]` で導入できます。

```console
pip install "strophe[converter]" # pip
```

```console
uv add "strophe[converter]" # uv
```

```python
import strophe

converted = strophe.download(
    ["ja", "fr"],
    processors="tokenize",
    resources_version="1.14.0",
)
```

`tokenize` に対応する MWT は resource catalog から自動追加されます。LangID
モデルも同じ公開 API で準備できます。

```python
strophe.download("multilingual", processors="langid")
```

MorphSeg は Stanza resources JSON に checkpoint がないため、公開済みの
Morphological-Segmentation safetensors を言語ごとの固定 commit と SHA-256
で取得します。対応言語は `cs,en,es,fr,hu,it,la,mn,ru`、concrete package 名は
`tuseg` です。

```python
strophe.download("en", processors="tokenize,morphseg")

pipeline = strophe.Pipeline(
    "en",
    processors="tokenize,morphseg",
    model_dir="models",
)
document = pipeline("Unhappiness")
print(document.sentences[0].words[0].morphemes)
```

## LangID・多言語 pipeline・bulk

`MultilingualPipeline` は LangID を一度 bulk 実行し、判定言語ごとに文書を
まとめて言語別 native `Pipeline` へ渡します。言語 pipeline は LRU cache
され、未変換モデルは既定で公開 resource catalog から遅延 download・変換
されます。

```python
import strophe

pipeline = strophe.MultilingualPipeline(
    processors="tokenize",
    package={"tokenize": "combined_nocharlm"},
    lang_configs={
        "ja": {},
        "fr": {
            "package": {"tokenize": "combined"},
        },
    },
    restrict=True,
)

documents = pipeline.bulk_process(
    [
        "これは日本語です。",
        "Ceci est une phrase française.",
    ]
)

assert [document.lang for document in documents] == ["ja", "fr"]
```

自動 download を禁止する場合は `auto_download=False` を指定します。通常の
`Pipeline` にも `bulk_process()` と同義の `process_many()` があり、
`pipeline([...])` も使えます。文字列列・`Document` 列のどちらも入力順を
保って処理します。

```console
uv add "strophe[converter]"
uv run strophe-convert convert \
  --lang en \
  --processors tokenize \
  --package default \
  --resources-version 1.14.0 \
  --output-dir models
```

英語の native UD 導線（tokenize → MWT → POS → lemma → depparse）を
まとめて変換する例です。tokenizer package に対応する MWT は catalog から
自動的に追加されます。

```console
uv run strophe-convert convert \
  --lang en \
  --processors tokenize,pos,lemma,depparse \
  --package combined_nocharlm \
  --resources-version 1.14.0 \
  --output-dir models
```

静的 embedding の NER profile は次のように変換できます。

```console
uv run strophe-convert convert \
  --lang en \
  --processors ner \
  --package ontonotes-ww-multi_nocharlm \
  --resources-version 1.14.0 \
  --output-dir models
```

英語の既定 sentiment profile と tokenizer は、依存する pretrain と双方向
charLM を含めて同じ公開 API から準備できます。

```python
from pathlib import Path

import strophe

model_dir = Path("models")
strophe.download(
    "en",
    processors="tokenize,sentiment",
    model_dir=model_dir,
    output_dir=model_dir,
)

pipeline = strophe.Pipeline(
    "en",
    processors="tokenize,sentiment",
    model_dir=model_dir,
)
document = pipeline("This movie was wonderful.")
assert document.sentences[0].sentiment in (0, 1, 2)
```

現在、公式 Trainer / model loader を経由する変換アダプタは `langid`、
`tokenize`、`mwt`、`pos`、`lemma`、`depparse`、`ner`、`sentiment`、
`constituency`、`coref`、`morphseg`、
`forward_charlm`、`backward_charlm` に対応しています。
POS/depparse/NER/sentiment/constituency の pretrain 行列・語彙と charLM
dependency も公式 loader で復元して bundle に内包します。checkpoint が
Transformer を使う場合は encoder と Stanza 固有の hidden-layer mix を一つの
AOTInductor package へコンパイルし、bundle 内の `transformer/` へ展開します。
生成 DSO と Transformer weight blob は分離し、weight は LibTorch loader が
mmap するため、大規模モデルでも巨大な DSO を dyld/loader に処理させません。
この artifact は変換時の OS、CPU architecture、Torch minor version を対象に
するため、利用する環境の `torch` で変換してください。未対応 processor を
raw checkpoint のまま変換する fallback は、旧 key migration や外部依存を
欠落させるため意図的に設けていません。

出力は Python pickle に依存しない次の構造です。

```text
models/1.14.0/en/tokenize/combined_nocharlm.strophe/
├── manifest.json
├── assets.json
├── tensors.bin
└── transformer/            # Transformer profile の場合だけ
```

`tensors.bin` は 64-byte alignment の raw tensor 列、`assets.json` は vocab、
辞書、config、`manifest.json` は source/dependency checksum、tensor table、
任意 artifact の checksum を保持します。詳細は
[`docs/model-bundle-v1.md`](docs/model-bundle-v1.md) を参照してください。

bundle は単独でも検証できます。

```console
uv run strophe-convert verify \
  models/1.14.0/en/tokenize/combined_nocharlm.strophe
```

native loader は bundle identity、tensor range/alignment/dtype/shape を常に
検証し、`tensors.bin` を read-only mmap します。公開 processor と `Pipeline`
の標準経路では、起動時に大きな model artifact を再走査しないよう SHA-256
検証を省略します。配布物の完全性も確認したい場合は
`verify_checksums=True`（`Pipeline` では
`verify_model_checksums=True`）を明示してください。低水準の
`ModelBundle` は検証用途のため既定で assets、tensor file、各 tensor と model
artifact の SHA-256 を検証します。

```python
import strophe

bundle = strophe.ModelBundle("models/1.14.0/en/tokenize/combined_nocharlm.strophe")

assert bundle.checksums_verified
assert bundle.tensor("embeddings.weight").shape == [240, 32]

pipeline = strophe.Pipeline(
    "en",
    model_dir="models",
    verify_model_checksums=True,
)
```

tokenizer は Stanza 1.14.0 と同じ character vocabulary/features、辞書
prefix/suffix 特徴、2段
bidirectional LSTM、token/sentence/MWT head、URL/email constraint、sentence
break による長文 restart、文字 offset と whitespace 復元を実装しています。
現在受理する native inference profile は多言語 no-charLM、hierarchical
one-layer、float32/CPU です。辞書 tokenizer を使う日本語、中国語（簡体・
繁体）、タイ語なども扱えます。未対応の bundle variant は近似実行せず
`TokenizerModelError` で拒否します。

LangID は Stanza の文字 embedding、multi-layer bidirectional LSTM、文字ごとの
linear score の総和を ATen で実行します。bulk 入力は code point 長ごとに
まとめて推論し、`langid_lang_subset` の score mask にも対応します。

MWT/lemma は、共通の bidirectional LSTM encoder、LSTMCell、soft dot
attention、copy gate、greedy/beam decoder を使います。beam は Stanza と同じ
累積 score、ATen `topk`、backpointer、top beam の EOS 終了規則を保持します。
lemma は POS 別辞書から `*` fallback の順に検索し、未登録語だけを seq2seq へ
送り、edit classifier と不正出力 fallback を適用します。MWT は辞書を優先し、
seq2seq checkpoint では同じ decoder を使います。Stanza 1.14.0 の英語
`combined` MWT は seq2seq ではなく character boundary classifier のため、
その BiLSTM/2-class path も実装しています。

POS は Stanza の頻出語 embedding、pretrain embedding、attention 付き character
LSTM、ATen charLM／LibTorch Transformer、2 層 bidirectional HighwayLSTM、UPOS
linear head、XPOS/UFeats biaffine head を checkpoint の構成と同じ順序で実行し、
`Word.upos`、`Word.xpos`、`Word.feats` を更新します。句読点簡約と語彙の
Unicode lower、言語により分解される composite XPOS の複数 head と再結合も
Stanza と一致させています。

depparse は word/lemma/POS/pretrain/character embedding、bidirectional
HighwayLSTM、deep biaffine の arc/relation head、linearization/distance 補正、
exactly-one-root Chu–Liu/Edmonds、subject/object の head constraint 修復を
ネイティブ実装し、`Word.head` と `Word.deprel` を更新します。複合 XPOS を
使う言語では component ごとの embedding を加算します。Stanza v1.8.2
以降のソースには、UFeats 用の入力枠へ誤って POS embedding を再度渡す回帰が
あります。対応対象の 1.14 公開チェックポイントはそのグラフで学習されており、
推論時だけ `ufeats_emb` に切り替えると未学習パラメータを使用します。このため
bundle は `duplicated_pos_emb` を明示的な実行契約として記録します。修正版
profile はモデルの再学習を必要とします。

NER は static word embedding と学習語彙の delta embedding、単語単位の
bidirectional character LSTM、ATen charLM／LibTorch Transformer、
bidirectional tagger LSTM、列別 linear head と CRF Viterbi 復号を実装します。
`predict_tagset` で公開列を選び、Stanza と同じ不正な singleton tag の修復後に
`Token.ner` と `Token.multi_ner` を設定し、`Document.ents` を再構築します。

sentiment は Stanza の pretrain 語彙検索、学習済み UNK vector、任意の
extra embedding、片方向または双方向 ATen charLM、LibTorch Transformer、任意の
2 層 bidirectional LSTM、多窓 2D convolution/max-pooling、全結合 head を
同じ順序で実行します。文ごとの入力には `Word` ではなく `Token.text` を使い、
予測 class index を `Sentence.sentiment` に設定します。`classify_many()` と
`bulk_process()` は文長で安定 sort し、Stanza と同じ「合計 token 数」単位の
batch を構成した後、元の順序へ戻します。現在の明示的な native profile は
CNN model type です。ELMo、PEFT、forward 中に重みを変更する extra-embedding
max-norm は近似せず変換時またはロード時に拒否します。Transformer profile は
共通変換器が受理する WordPiece、RoBERTa byte-level BPE、XLM-R Unigram 構成を
利用できます。

constituency は `Word.text` と checkpoint が指定する XPOS/UPOS を入力にし、
pretrain/delta/tag embedding、双方向 word LSTM、transition/constituent 履歴
LSTM、in-order transition legality、MAX constituent composition を ATen で
実行して `Sentence.constituency` を設定します。`parse()`、`parse_many()`、
`bulk_process()` も公開します。現在の明示的な native profile は
`LSTMModel + IN_ORDER + MAX + LSTM history + ReLU` です。parser attention、
Tree-LSTM 系 composition、maxout、PEFT は誤って近似せず変換時またはロード時に
拒否します。Transformer profile は共通変換器が受理する WordPiece、RoBERTa
byte-level BPE、XLM-R Unigram 構成を利用できます。

charLM は文字 embedding と forward/backward LSTM を ATen で実行し、Stanza と
同じ改行・空白境界から単語表現を採取します。POS、depparse、NER、sentiment、
constituency に埋め込まれた dependency と、単独の `CharLMPlan` の両方で
利用できます。

Transformer の subword tokenizer は C++ で実装しています。BERT normalizer +
greedy WordPiece に加え、GPT-2/RoBERTa の byte alphabet・Unicode pretokenizer・
ranked BPE merge と、XLM-R の SentencePiece precompiled charsmap・Metaspace・
Unigram Viterbi を扱います。ASCII lowercase の hot path は Highway、反復する
pretoken は固定長 cache を使い、各単語を Stanza と同じ subword alignment へ
戻します。長文は processor ごとの overlap 規則で分割します。実装方針は
gigatoken の短い token に対する単純な merge、cache、allocation 抑制を参考に
しています。

coref は Stanza の `CorefModel.load_model` で checkpoint と PEFT adapter を
復元し、`safe_merge` 後の出力一致を確認してから基底 encoder を
`torch.export` し、AOTInductor package にコンパイルします。非 LoRA checkpoint
は再構築した full-finetune／base encoder を同じ経路でコンパイルします。
推論時は Python package の LibTorch が選ぶ SDPA と BLAS backend を使います。
ネイティブ側では sentence-aware subword window、学習済み subword attention、
rough mention/antecedent scoring、distance/speaker feature、anaphoricity head、
span head と zero-anaphora head を実行し、`CorefChain` と各 word の
`CorefAttachment` を復元します。zero mention は Stanza と同じ `(word,
empty_index)` ID を持つ empty `Word` を生成し、その word に attachment を
接続します。antecedent FFNN は checkpoint の `a_scoring_batch_size` 境界で
chunk 化し、長文で完全な pair tensor を保持しません。
各 sentence-aware window は動的 sequence 長のまま実行し、短い文を512
subword まで埋める masked padding は計算しません。実 token の Transformer
出力は固定長 padding 時と同一です。
`CorefProcessor.process_bulk()` は入力順を保持します。

大文字・小文字変換と `isupper` / `islower` 判定は ASCII 近似ではなく、
Python 3.13 と同じ Unicode 15.1 table を生成物として固定しています。表は
`tools/generate_unicode_case.py` で再生成できます。

## レイアウト

```text
src/
├── strophe/                 # Python API
└── libstrophe/
    ├── include/strophe/     # 公開 C++ ヘッダー
    └── lib/                 # C++ 実装と nanobind bindings
```

## ライセンス

Stropheは[Apache License 2.0](LICENSE)で提供されます。binary wheelへ組み込まれる
第三者ソフトウェアの表示は[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)と
[`LICENSES/`](LICENSES/)を参照してください。モデル資産の扱いは
[ドキュメント](docs/license.md)に分離して記載しています。
