A Goblin Reactor product

stride-align

Smith-Waterman & Needleman-Wunsch · 简体中文

比 rapidfuzz 更快,比 parasail 更快,一行 import 即可替换。

Languages: English · 简体中文

stride-align 是一个 SIMD 加速的 Python 库,专注于模糊字符串匹配、 序列比对、语音编码与时间序列距离——把 Unicode/CJK 作为一等公民, 并由运行时 CPU dispatch 自动选用当前机器支持的最宽 SIMD 后端 (x86、ARM、龙架构、POWER),并带标量回退。

它同时是四个常用库的直接替代品: import stride_align.rapidfuzz as rapidfuzz 替代 rapidfuzzfrom stride_align.thefuzz import fuzz, process 替代 TheFuzzimport stride_align.parasail as parasail 替代 parasail-pythonimport stride_align.jellyfish as jellyfish 替代 jellyfish—— 现有代码只需改一行 import,即可跑在原生 SIMD 内核上。 (新代码应优先直接使用原生 stride_align API。)

完整的功能清单、所支持的全部算法以及各后端的细节,请见 docs/api/ 下的 API 参考,以及面向 LLM 的 llms.txtllms-full.txt

这里不讲理论,直接动手——边做边学。

快速开始

pip install stride-align

预编译 wheel 覆盖 Linux x86_64(glibc 与 musl)、macOS arm64、 Linux aarch64、Linux ppc64le 在 CPython 3.12 / 3.13 / 3.14 上的 组合。其他(平台,Python)组合会回退到 PyPI 上的源码发行版并在 本地编译,这需要支持 C++20 的编译器和 CMake ≥ 3.26。

龙芯 / 龙架构(LoongArch64)用户: wheel 不在 PyPI 上,发布 渠道是 GitHub Releases,安装时还需要在老世界与新世界两套二进制 世界之间二选一——详见后面的 龙架构 LoongArch 安装

先声明一下:这里用宗教文本不是要带任何立场——这个 demo 需要几份 比较大的、含义相同但表达不同的公共领域文档,圣经恰好极其符合这个 要求。

假设我们有两个句子——用《创世记》的开篇:

美国标准译本(American Standard Version,ASV)是这样的:“In the beginning God created the heavens and the earth.”

英王钦定版(King James Version,KJV)则是:“In the beginning God created the heaven and the earth.”

肉眼就能看到区别——heavens 还是 heaven。但怎么把这种差异量化?用 下面这一小段代码:

import stride_align as sa

print(sa.smith_waterman_normalized_score(
      "In the beginning God created the heavens and the earth.",
      "In the beginning God created the heaven and the earth."))

运行后输出:

0.9907407407407407

归一化得分在 01 之间。得分为 1 表示在默认评分模型下两个 输入完全匹配。得分接近 0 表示两份输入几乎没有共同之处,不过 Smith-Waterman 仍可能在原本不相关的字符串内部找到小的局部匹配。

现在换一下文本,看看得分怎么变。

import stride_align as sa

print(sa.smith_waterman_normalized_score(
      "In the beginning God created the heavens and the earth.",
      "The quick brown fox jumped over the lazy dog."))

Python 输出:

0.12222222222222222

明白了吗?字符串越相似,得分越高。

我们来做个稍大点的例子,感受一下这个库的性能。你可能会注意到我们 在 Smith-Waterman 和 Needleman-Wunsch 之间切换,大概会想什么时候 用哪一个。当你想拿整段输入与整段输入比较时,用 Needleman-Wunsch。 当你想在较大的输入内部找出匹配最好的区域时,用 Smith-Waterman。

好,进入 demo 代码。这一部分需要 requests

pip install requests
import os, time, requests
import stride_align as sa

if not os.path.exists("kjv.txt"):
    response = requests.get("https://openbible.com/textfiles/kjv.txt")
    response.raise_for_status()
    response.encoding = "utf-8-sig"
    open("kjv.txt", "w", encoding="utf-8").write(response.text)

lines = [line.strip().lower() for line in open("kjv.txt")][2:]

while True:
    if not (query := input("Enter a snippet to match.  Press enter to end.\n")):
        break
    t = time.perf_counter()
    scores = sa.needleman_wunsch_normalized_scores(query.lower(), lines)
    best = int(scores.argmax())
    print()
    print("Score:", float(scores[best]))
    print(lines[best])
    print("Search time: %0.2fms" % ((time.perf_counter() - t) * 1000))
    print()
    print()

这能拿来干什么?假设我们手上有一段圣经经文,想知道它出自哪一章 哪一节。你说用 grep?哎,不行,这里有个问题:我们手上的经文是 另一个译本,比方说 Catholic Public Domain 译本,而电脑上的是 King James 版本。grep 的精确匹配在这里行不通。怎么找到对应的 章节?当然是用 stride-align 搜索“最接近”或“最相似”的字符串。

demo 的第一部分处理下载和缓存。Open Bible 把这份文本放在了 HTTP 可访问的位置,我们要尊重他们的带宽,所以 下载下来就缓存住,做个体面的网络公民。

接下来把所有行加载到一个列表里。我们去掉换行符,把所有内容转成 小写——免得在 Shift 键问题上较劲。

最后那段 while True: 循环读取一行文本——大概就是来自 Catholic 版本、我们想查章节的那句经文——用 Needleman-Wunsch 的批处理形式 把它和 King James 版本里所有行做匹配。它返回一个得分数组。我们 用 argmax() 找到最高分对应的行,再把对应索引的那行打印出来。 试一下。

我准备用 Catholic 圣经里的 Jeremiah 4:28——它和 King James 圣经 里的同一节其实差别挺大。来看看效果……

$ python3 demo2.py
Enter a snippet to match.  Press enter to end.
The earth will mourn, and the heavens will lament from above. For I have spoken, I have decided, and I have not regretted. Neither will I be turned away from it.

Score: 0.3598901098901099
jeremiah 4:28   for this shall the earth mourn, and the heavens above be black: because i have spoken [it], i have purposed [it], and will not repent, neither will i turn back from it.
Search time: 206.51ms

……找到了!而且相当快。

再来一个 demo:拼写检查。

这是一个玩具拼写检查器,不是生产级的。它忽略标点、大小写、词频、 专有名词和上下文。重点是用一个熟悉的任务展示同样的“一对多查询” 模式。

import os, sys
import stride_align as sa

paths = ['/usr/share/dict/words',
         '/usr/dict/words',
         '/var/lib/dict/words',
         '/etc/dictionaries-common/words']

for path in paths:
    if os.path.exists(path):
        break
else:
    print("Sorry, I can't find your dictionary", file=sys.stderr)
    exit(1)


words = [line.strip().lower() for line in open(path)]


for line in sys.stdin:
    new_line = []
    for word in line.split():
        scores = sa.needleman_wunsch_normalized_scores(word.lower(), words)
        word = words[int(scores.argmax())]
        new_line.append(word)
    print(' '.join(new_line), flush=True)

脚本做的第一件事是尝试找到操作系统里的拼写正确词表。它的位置因 发行版而异。找到之后,加载、去掉换行符,然后开始拼写检查。

拼写检查本身跟前面的匹配很像。对每个输入词,把它和正确词表里的 所有词做匹配,用 argmax() 找到分最高的那个,然后用它替换。 这里还有不少可以优化的地方,比如对已经拼对的词直接跳过查找, 不过这是个 demo,这点优化就留作读者练习了。

来看看效果!

$ cat - | python3 demo3.py
this is a demonstrtion of a spel checker
it doesn't matter that I can't spell corectly

this is a demonstration of a spell checker
it doesn't matter that i can't spell correctly

细节

当前实现提供:

C++/Python 边界接受:

直接拿 bytesstr 配对会抛出 TypeError

当前实现是通用的动态规划内核,配上把 Python 输入序列化成 8、16、 32 或 64 位 token 流的预处理。SIMD 专门化的 backend 之后可以替换 对应的 backend 编译单元,而不动 Python API。

仅得分函数返回数值得分。归一化版本返回 01 之间的分数。 路径函数返回对齐结果对象,里面包含得分、对齐后的值、操作序列, 以及可用时的 CIGAR 摘要。

API

import stride_align

score = stride_align.needleman_wunsch_score("ACGT", "ACCT")
scores = stride_align.Scores("ACGT", variant="needleman_wunsch").compare(["ACCT", "AGGT"])
result = stride_align.smith_waterman_path("ACCGT", "CCG")
wide_result = stride_align.smith_waterman_path("ACCGT", "CCG", width=64)
object_result = stride_align.needleman_wunsch_path(
    [frozenset({1}), frozenset({2})],
    [frozenset({1}), frozenset({3})],
)

print(score)
print(scores)
print(result.score, result.aligned_query, result.aligned_target, result.operations)
print(wide_result.score)
print(object_result.aligned_query, object_result.aligned_target)

“一个 query 对多个 target”的得分工作流,请使用 Scores(...).compare([...]) 或者 *_scores() 函数。这条路径 会一次性准备好 query / profile,是反复做中英文文本比对时推荐的 高性能 API。

对于直接配对的快速路径,回溯输出会保留输入类型:

width=8163264 可以强制内部 token / 评分宽度, 而不是用自动选择。

部分函数会暴露 CIGAR 字符串——CIGAR 是 “Concise Idiosyncratic Gapped Alignment Report” 的缩写,是 SAM/BAM 工具链使用的紧凑 对齐操作记法。如果想看完整规范,见 SAM specification

替换矩阵(BLOSUM、PAM)

针对蛋白质比对,stride_align.matrices 提供了规范的 BLOSUM 与 PAM 替换矩阵。可通过 smith_waterman_scoreneedleman_wunsch_score 及其 _scores 批量版本上的 matrix= 关键字传入:

import stride_align
from stride_align.matrices import blosum62, pam250

# 局部比对,NCBI 标准 BLOSUM62 + 仿射空位(open=-11, extend=-1)。
# matrix= 与 match_score / mismatch_score 互斥。
stride_align.smith_waterman_score(
    "HEAGAWGHEE", "PAWHEAE",
    matrix=blosum62,
    gap_open_score=-11, gap_extend_score=-1,
)

# 批量(1 条查询 × N 条目标),共用一次构建好的剖面 —— 推荐用法。
stride_align.smith_waterman_scores(
    "HEAGAWGHEE",
    ["PAWHEAE", "HEAGAWGHEE", "MEEPS"],
    matrix=pam250, gap_open_score=-14, gap_extend_score=-2,
)

# 自定义矩阵:直接解析 NCBI 文本格式
custom = stride_align.matrices.SubstitutionMatrix.from_ncbi_text(
    open("/path/to/BLOSUM62").read(),
    name="BLOSUM62",
    gap_open=-11, gap_extend=-1,
)

每个内置 SubstitutionMatrix 都暴露字母表、矩阵数据 (int8 ndarray)以及推荐的空位默认值 .gap_score(线性)、 .gap_open.gap_extend。线性空位(gap_score=)与仿射空位 (gap_open_score= + gap_extend_score=)在 AVX-512 后端都受支持; 其它 SIMD 后端目前对矩阵模式会回退到标量通用内核。

矩阵数值来自 NCBI BLAST 发行版 ftp.ncbi.nih.gov/blast/matrices/, 即规范的参考分值。原始文献为:

编辑距离评分器

除了 Smith-Waterman 和 Needleman-Wunsch,stride-align 还提供六种 单位代价的编辑距离/相似度度量。其中大多数都有 SIMD 批处理代码 路径;true-DL 目前是例外,仍然走标量 DP:

import stride_align

# Levenshtein(Myers 1999 位并行)——插入、删除、替换
stride_align.levenshtein_score("kitten", "sitting")               # -> 3
stride_align.levenshtein_normalized_score("kitten", "sitting")    # -> 0.571...
stride_align.levenshtein_scores("kitten", ["kit", "sitting"])     # -> ndarray[int64]

# 可选的 `score_cutoff`(rapidfuzz 的约定):按 target 提前退出,
# 超过 cutoff 的结果以 `cutoff + 1` 形式返回。
stride_align.levenshtein_scores(query, targets, score_cutoff=3)

# Damerau-Levenshtein(OSA 受限版,Hyyrö 2002)——在单位代价
# 下增加相邻字符的转置。这是 rapidfuzz 用 OSA.distance 暴露的
# 那个,也是大多数人嘴里说的“Damerau-Levenshtein”实际上想要的。
stride_align.damerau_levenshtein_score("ab", "ba")                # -> 1

# True Damerau-Levenshtein ——不受限版本,一个字符可以参与多次
# 编辑。较慢(目前没有位并行内核),但与
# rapidfuzz.distance.DamerauLevenshtein 完全一致。和 OSA 在重叠
# 转置上会分叉,例如 "ca" -> "abc":OSA=3,true-DL=2。
stride_align.true_damerau_levenshtein_score("ca", "abc")          # -> 2

# Indel ——仅限插入和删除的 Levenshtein,不允许替换。
# 等价于 |a| + |b| - 2 * LCS(a, b)。Allison-Dix(1986)位并行
# 内层循环。
stride_align.indel_score("kitten", "sitting")                     # -> 5

# Hamming ——两个等长字符串中位置不同的个数。
# 带 cutoff 的变体在累计差异超出上限时直接退出。
stride_align.hamming_score("100", "110")                          # -> 1

# Jaro / Jaro-Winkler ——[0, 1] 区间的相似度;Winkler 在前缀
# 上加一个有上限的加成。
stride_align.jaro_similarity("martha", "marhta")                  # -> 0.944...
stride_align.jaro_winkler_similarity("martha", "marhta")          # -> 0.961...

批处理变体(*_scores*_similarities)在每一个支持的 backend 上都按“一个 target 一个 SIMD lane”的方式打包起来:

对 Lev / OSA 而言,长度 ≤ 64 的模式走单字 Myers;65–256 走多字 内核(W=2/3/4)。Indel 在模式长度 > 64 时回退到标量位并行 (多字推广暂未实现);true-DL 目前只有标量 DP。

语音编码器(Phonetic encoders)

对于姓名匹配、去重和即时搜索这类场景,stride-align 提供完整的 标准语音编码器家族。每个编码器把字符串映射成一个短代码,发音 相近的名字共享同一个代码,与拼写差异无关:

import stride_align as sa

# American Soundex(Russell & Odell, 1918)。4 字符代码。
sa.soundex("Robert")                                     # -> "R163"
sa.soundex("Rupert")                                     # -> "R163"
sa.soundex_equal("Robert", "Rupert")                     # -> True

# Metaphone(Lawrence Philips, 1990)——两个版本:1990 规范本身
# 和广为使用的 jellyfish 库在少数边界情况上有分歧,variant 参数
# 选择对应的规则家族。
sa.metaphone("Schmidt")                                  # -> "SKMTT"  (PHILIPS, 规范)
sa.metaphone("Schmidt", variant=sa.MetaphoneVariant.JELLYFISH)  # -> "SXMTT"
sa.metaphone_equal("Schmidt", "Smith")                   # -> False

# Double Metaphone(Lawrence Philips, 2000)——返回 primary 与
# alternate 两个代码;alternate 捕捉合理的非英语发音变体。
# COMMONS 忠实复刻 Apache Commons Codec;PYTHON 与 PyPI 的
# metaphone 包按位兼容。
sa.double_metaphone("Schwartz")                          # -> ("XRTS", "XFRTS")
sa.double_metaphone("Hugh")                              # -> ("H", "")
sa.double_metaphone("Hugh",
    variant=sa.DoubleMetaphoneVariant.PYTHON)            # -> ("HH", "")

# NYSIIS(Taft, 1970)。对英文人名比 Soundex 更具区分度——
# "Watkins" / "Wilkins" / "Wilkinson" 不会冲突。
sa.nysiis("Watkins"), sa.nysiis("Wilkins")               # -> ("WATCAN", "WALCAN")

# Match Rating Approach(Moore, Western Airlines, 1977)。
# 包含一个 codex 和一个配对比较器;后者用长度差和评分阈值
# 两条规则判定是否匹配。
sa.match_rating_codex("Christopher")                     # -> "CHRPHR"
sa.match_rating_compare("Robert", "Rupert")              # -> True

# Caverphone 2.0(Hood, 2004)。固定长度 10 字符代码,
# 不足时用 '1' 右填充。最初是为 19 世纪末新西兰选民名册设计的,
# 现广泛用于一般英语姓名匹配。
sa.caverphone("Stevenson")                               # -> "STFNSN1111"

这七个编码器共享同一套字节提取助手,strbytes 输入可以 互换,编码前会跳过非字母 / 非 ASCII 码点——如果需要去掉重音 符号,请先用 unicodedata.normalize("NFKD", s) 预处理。所有结果 都与 Apache Commons Codec 的规范参考数据,以及 jellyfishmetaphonedoublemetaphone 这几个 PyPI 包做了交叉验证。

动态时间规整(Dynamic Time Warping)

对于时序速度不一致的数值序列比对——音频信号、手势 / 传感器轨迹、 金融时间序列——stride-align 提供带 Sakoe-Chiba 窗口的动态时间 规整:

import numpy as np
import stride_align as sa

q = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
t = np.array([1.0, 2.0, 2.5, 4.0, 5.0])

# 默认距离按 dtype 选择:
#   float32 / float64 -> L2 平方,(x - y)^2
#   int16             -> L1,|x - y|(音频约定)
sa.dtw(q, t)                                              # -> 0.25

# Sakoe-Chiba 窗口:整数半径,或 max(|q|, |t|) 的小数比例。
sa.dtw(q, t, window=2)
sa.dtw(q, t, window=0.2)

# 显式指定距离。
sa.dtw(q.astype(np.int16), t.astype(np.int16), distance="l1")

# 批处理。
sa.dtw_distances(q, [t, t * 2, t + 0.5], window=2)

输入必须是 dtype 相同的 NumPy ndarrayfloat32float64int16——int16 是天然的音频 dtype)。其它 dtype 与非 ndarray 输入会抛出 TypeError

cdistcdist_above_thresholdcdist_top_kcdist_top_k_per_query

针对两份字符串列表的全配对评分场景,stride-align 提供三个 矩阵风格的入口:

qs = ["kitten", "sitting", "kit"]
ts = ["kitten", "kit", "sitting", "biting"]

# 完整 N×M 相似度矩阵 —— ndarray[float64](相似度评分器)
# 或 ndarray[int64](距离评分器)。
sa.cdist(qs, ts, scorer=sa.Scorer.JARO)

# 流式过滤 ——只产出相似度超过阈值的对。worker 向一个有界队列喂数据,
# 调用方负责消费。长度剪枝 + 把每对的 cutoff 下推到 kernel 里,
# 在高阈值下能跳过绝大部分工作。
for score, q, t in sa.cdist_above_threshold(
    qs, ts, scorer=sa.Scorer.LEVENSHTEIN_NORMALIZED, threshold=0.7,
):
    ...

# 按得分取 top-k ——返回最多 k 个最高得分(对距离评分器则是
# 最低)的 (score, query, target) 三元组。每个线程一个堆;
# 一个共享的原子全局最小值边界,让每对的 cutoff 下推
# 在工作推进时不断抬高剪枝阈值。
sa.cdist_top_k(qs, ts, scorer=sa.Scorer.JARO, k=10)

# 每个 query 取 top-k ——以生成器形式 yield。与 cdist_top_k
# 不同(后者返回 query × target 矩阵中全局得分最高的 k 对),
# 本函数为每个 query 各自维护一个 top-k 堆。设置 pruning=True
# 后,会跟踪当前堆中最差的得分;当一个 target 的长度差闭式
# 相似度上界证明无法越过该得分时,跳过评分内核——在长度差
# 较大的工作负载上能带来显著加速。
for query, top in sa.cdist_top_k_per_query(
    qs, ts, scorer=sa.Scorer.LEVENSHTEIN_NORMALIZED, k=5, pruning=True,
):
    # top 是 [(score, target), ...],按得分降序排好
    ...

在高阈值下剪枝的效果非常夸张——见 BENCHMARK 中的跨架构表(cdist pruning 那几行)。特别是龙芯(Loongson)的 LASX 后端在 T=0.99 下,让通常预期会领先的 Tiger Lake AVX-512 反而落后;对比报告见 docs/loongson-vs-tiger-lake-cdist-2026-05-24

完整的跨架构数据见 BENCHMARK

优化和基准测试

stride-align 的性能一直被认真对待,而且仍在持续优化。这个库为 x86、Arm、龙架构(LoongArch)等多个常见目标做了 SIMD 优化。

Intel AVX-512 对 Parasail(2026-07-18)。 当前固定核基准运行在 Granite Rapids Xeon 6975P-C 上,使用 GCC 16.1 与 Parasail 1.3.4。 80 个可直接比较的组合覆盖英文和中文输入、线性和仿射空位、16/32 位得分宽度、1:11:many 两种形状,以及七种评分、路径和 CIGAR 变体。比值为 Parasail 中位运行时间除以 stride-align 中位 运行时间,因此大于 1 表示 stride-align 更快。

AVX-512 分组 胜出项 几何平均值 中位数
全部 79 / 80 1.682x 1.656x
仅评分 47 / 48 1.559x 1.558x
路径 / CIGAR 32 / 32 1.883x 1.737x

表现最强的变体是 Needleman-Wunsch 路径(2.356x)、 Needleman-Wunsch CIGAR(2.341x)和 Smith-Waterman 评分(1.989x)。 AVX-512 唯一落后的项目是中文、线性空位、1:1、16 位宽度的 Needleman-Wunsch 评分(0.874x)。完整方法、AVX2 对照、逐变体表格 和最慢五项见 BENCHMARK。规范原始数据为 benchmark.csv,并保留了不可变的 日期快照

龙架构(LoongArch)/ 龙芯(Loongson)。 龙芯的优化故事尤其 值得一说:在基准用例下(英文文本、16 位得分宽度、仅得分的 Smith-Waterman),LASX backend 比 generic backend 快 16 倍, 比 Parasail 快 22.4 倍

如果你是在龙芯服务器上做研究,并且从这份提速里得到好处的研究者, 欢迎引用、提交 bug、贡献基准用例;如果你愿意表达一点心意,我也很喜欢收到一些不贵的中国小纪念品—— 茶叶、书法书签、剪纸、中国结、熊猫钥匙扣、小龙摆件之类都很好。 请不要寄贵重物品或需要报关的东西。

完整基准测试见 BENCHMARK

原生微基准

如果想在不经 Python 帧、不走基准编排的情况下做性能分析,可以配置 一份原生 x86 microbench 构建:

nanobind_dir="$(.venv/bin/python -m nanobind --cmake_dir)"
cmake -S . -B build/perf \
  -DCMAKE_BUILD_TYPE=RelWithDebInfo \
  -DSTRIDE_ALIGN_BUILD_MICROBENCH=ON \
  -DSTRIDE_ALIGN_PERF_SYMBOLS=ON \
  -DPython_EXECUTABLE=.venv/bin/python \
  -Dnanobind_DIR="$nanobind_dir"
cmake --build build/perf --target stride_align_x86_microbench
build/perf/stride_align_x86_microbench --backend avx2 --shape 1:many --pass english --width 16
python tools/x86_microbench_regression.py \
  --binary build/perf/stride_align_x86_microbench \
  --cpu 2 \
  --backends avx2,avx512bwvl \
  --shapes 1:1,1:many \
  --passes english,chinese \
  --widths 16,32 \
  --write-json /tmp/stride-align-x86-microbench.json
.venv/bin/python tools/pinned_benchmark_sweep.py \
  --output-dir /tmp/stride-align-pinned \
  --cpu 2 \
  --iterations 15 \
  --warmups 3

STRIDE_ALIGN_PERF_SYMBOLS=ON 会保留 nanobind 模块不被 strip, 并加入调试符号和 frame pointer,同时保持 -O3

签入仓库的原生 microbench 基线在 benchmarks/x86_microbench_baseline.json。把它当作一个宽松阈值的 本地兜底,不要当成跨机器的 SLA。

龙架构 LoongArch 安装

龙架构的 wheel 不在 PyPI 上(PyPI 暂不索引 linux_loongarch64 平台标签),所以走的是另一条发布通道:

渠道 URL 前缀
GitHub Releases(主渠道) https://github.com/adamdeprince/stride-align/releases/download/v0.5.0/
stride-align.com 镜像 https://stride-align.com/wheels/v0.5.0/

两个渠道提供完全相同的 wheel,挑访问更快的那个用。在国内访问 GitHub 较慢时,镜像比较方便;GitHub Releases 是规范主页。

先用发行版安装 NumPy(PyPI 上的 loongarch64 NumPy wheel 还很 稀疏,发行版的版本通常和系统其他组件 ABI 兼容):

sudo apt install python3-numpy
PY=$(python3 -c 'import sys; print(f"cp{sys.version_info.major}{sys.version_info.minor}")')

老世界 vs 新世界:怎么选

龙架构硬件运行在两套互不兼容的二进制世界之一。两者在两个地方 不一样:

  1. 可执行文件引用的动态加载器(这个文件名是链接时固化进 ELF 头里的)。
  2. 二进制依赖的 glibc ABI 版本

一个世界编译出的 wheel 无法在另一个世界加载——加载器文件名在 对面那边根本不存在,就算存在符号也会对不上。我们为每个世界提供 一份独立的 wheel:

世界 加载器 glibc 典型机器 wheel build 标签
老世界 /lib64/ld.so.1 2.28 时代 出厂 Kylin、原版龙芯发行版 1.oldworld
新世界 /lib64/ld-linux-loongarch-lp64d.so.1 ≥ 2.36 较新的龙架构发行版,或者任何安装了新加载器的机器 1.newworld

两份 wheel 都静态链接了 libstdc++ / libgcc,唯一的差别就是 加载器和 glibc ABI 世界。

用下面这一行命令判断

test -e /lib64/ld-linux-loongarch-lp64d.so.1 && echo new-world || echo old-world

如果输出 new-world,说明加载器已就位——直接用新世界的 wheel。 如果输出 old-world,要么装老世界的 wheel,要么跑下面那条 一次性的 sudo 软链接命令进入新世界(安全——它是新增一个文件名而 不是替换,老世界的二进制照样正常)。

老世界 wheel

pip install \
  https://github.com/adamdeprince/stride-align/releases/download/v0.5.0/stride_align-0.5.0-1.oldworld-${PY}-${PY}-linux_loongarch64.whl

镜像:

pip install \
  https://stride-align.com/wheels/v0.5.0/stride_align-0.5.0-1.oldworld-${PY}-${PY}-linux_loongarch64.whl

新世界 wheel

新世界 wheel 需要新的加载器出现在 ELF 引用的那个路径上。每台 机器一次性 sudo 即可,不影响已有的老世界二进制:

sudo ln -sf /opt/loongson-gcc-16.1.0/sysroot/lib64/ld-linux-loongarch-lp64d.so.1 \
            /lib64/ld-linux-loongarch-lp64d.so.1

(发行版打包者通常会把同等的软链接随新世界一起放进来,那种情况下 可以跳过这一步。)

然后:

pip install \
  https://github.com/adamdeprince/stride-align/releases/download/v0.5.0/stride_align-0.5.0-1.newworld-${PY}-${PY}-linux_loongarch64.whl

镜像:

pip install \
  https://stride-align.com/wheels/v0.5.0/stride_align-0.5.0-1.newworld-${PY}-${PY}-linux_loongarch64.whl

其他说明

预编译的龙架构(LoongArch64)wheel 在两个镜像上都覆盖 Python 3.12、3.13 和 3.14——两个世界都有。如果你用的是其他 Python 版本,或者想从源码构建,pip install stride-align 会回退到 PyPI 上的源码发行版,在本地编译 LSX/LASX 内核。构建细节 (工具链、RPATH wrapper、静态 C++ 运行时)见 docs/loongson-build

引用

如果你在研究中用到了我的软件,请引用我。

@software{deprince_stride_align,
  author       = {DePrince, Adam},
  title        = {stride-align: Fast Smith-Waterman and Needleman-Wunsch alignment for Python},
  year         = {2026},
  publisher    = {GitHub},
  url          = {https://github.com/adamdeprince/stride-align},
  note         = {Python/C++ library for sequence and string alignment}
}