Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f71985c0c3 | |||
| 7952207735 | |||
| dd5fab9540 | |||
| a1e43bb679 | |||
| 0751449021 | |||
| 69f7f7a34b | |||
| 7a06f94577 | |||
| e93bc20b0a | |||
| 12e4266950 |
149 changed files with 530538 additions and 8 deletions
50
.gitignore
vendored
50
.gitignore
vendored
|
|
@ -1,2 +1,48 @@
|
||||||
slime
|
# Local build output (source assembly snapshots are intentionally retained).
|
||||||
slime-cuda
|
bin/
|
||||||
|
build/
|
||||||
|
cmake-build-*/
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
*.d
|
||||||
|
*.ptx
|
||||||
|
*.cubin
|
||||||
|
*.fatbin
|
||||||
|
/slime
|
||||||
|
/slime-cuda
|
||||||
|
/sliming
|
||||||
|
|
||||||
|
# Python environments and caches.
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# Temporary logs and scratch files, not benchmark JSON or research fixtures.
|
||||||
|
*.log
|
||||||
|
*.stderr
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
/exp/*.smt2
|
||||||
|
/artifacts/
|
||||||
|
|
||||||
|
# Machine-local configuration and credentials.
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
/.aws/
|
||||||
|
/.codex/
|
||||||
|
/.agents/
|
||||||
|
/.idea/
|
||||||
|
/.vscode/
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
|
||||||
42
Makefile
42
Makefile
|
|
@ -1,10 +1,40 @@
|
||||||
all: slime slime-cuda
|
CXX ?= g++
|
||||||
|
NVCC ?= nvcc
|
||||||
|
CXXFLAGS ?= -O3 -std=c++17
|
||||||
|
NVCCFLAGS ?= -O3 -std=c++17
|
||||||
|
|
||||||
|
# Keep the original default build compatible; use `make bench` for the current CLI.
|
||||||
|
.PHONY: all legacy bench research show tools test test-gpu clean help
|
||||||
|
all: legacy
|
||||||
|
legacy: slime slime-cuda
|
||||||
slime: slime.cpp
|
slime: slime.cpp
|
||||||
g++ -o slime slime.cpp -O3 -std=c++17
|
$(CXX) $(CXXFLAGS) $< -o $@
|
||||||
|
|
||||||
slime-cuda: slime.cu
|
slime-cuda: slime.cu
|
||||||
nvcc -o slime-cuda slime.cu -O3 -std=c++17
|
$(NVCC) $(NVCCFLAGS) $< -o $@
|
||||||
|
bench:
|
||||||
|
$(MAKE) -C bench
|
||||||
|
research:
|
||||||
|
$(MAKE) -C research
|
||||||
|
show:
|
||||||
|
$(MAKE) -C show
|
||||||
|
tools:
|
||||||
|
$(MAKE) -C tools
|
||||||
|
test:
|
||||||
|
$(MAKE) -C bench test
|
||||||
|
test-gpu:
|
||||||
|
$(MAKE) -C bench test-gpu
|
||||||
clean:
|
clean:
|
||||||
rm -f slime slime-cuda
|
rm -f slime slime-cuda sliming
|
||||||
|
$(MAKE) -C bench clean
|
||||||
|
$(MAKE) -C research clean
|
||||||
|
$(MAKE) -C show clean
|
||||||
|
$(MAKE) -C tools clean
|
||||||
|
help:
|
||||||
|
@echo 'make bench Build the current CUDA benchmark CLI'
|
||||||
|
@echo 'make test Run CLI host-side checks (nvcc required, GPU not required)'
|
||||||
|
@echo 'make test-gpu Run CLI GPU integration checks'
|
||||||
|
@echo 'make legacy Build the original CPU/CUDA brute-force programs (default)'
|
||||||
|
@echo 'make research Build research prototypes'
|
||||||
|
@echo 'make show Build teaching examples'
|
||||||
|
@echo 'make tools Build the single-chunk checker'
|
||||||
|
@echo 'make clean Remove compiled outputs; retain reports and datasets'
|
||||||
|
|
|
||||||
56
README.md
Normal file
56
README.md
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# 史莱姆区块搜索与 CUDA 基准
|
||||||
|
|
||||||
|
当前可直接使用的入口是 **[bench/slime-bench](bench/README.md)**:指定中心区块、曼哈顿半径和自定义图案(或实心 `n×n`),测量全部有效种子的普通候选分支搜索时间,支持单张或多张 NVIDIA GPU。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make bench
|
||||||
|
./bench/bin/slime-bench --x 0 --z 0 --radius 512 --size 5 \
|
||||||
|
--position-batch 128 --devices all --repeat 3
|
||||||
|
```
|
||||||
|
|
||||||
|
自定义图案用 `--pattern` 或 `--pattern-file`,支持史莱姆、非史莱姆和忽略格;见 [已验证的 5×5 爱心](bench/README.md#已验证实例5x5-爱心)及 [6×6 苦力怕示例](bench/README.md#自定义图案6x6-苦力怕)。
|
||||||
|
|
||||||
|
H100 编译:`make -C bench -B CUDA_ARCH=90`。需要 CUDA Toolkit 和 C++17 编译器。普通分支不包含拒绝重抽补扫,不能将它的结果声明为严格完整目录;参数和计时口径见 [CLI 使用说明](bench/README.md)。
|
||||||
|
|
||||||
|
## 目录导航
|
||||||
|
|
||||||
|
| 路径 | 用途 |
|
||||||
|
|---|---|
|
||||||
|
| [`bench/`](bench/README.md) | 当前 CLI、测试、已保存的本机计时样例 |
|
||||||
|
| [`include/`](include/README.md) | CLI 与研究代码共用的算法实现 |
|
||||||
|
| [`research/`](research/README.md) | 求解原型、算法实验、正确性验证和测量报告 |
|
||||||
|
| [`research/experiments/`](research/experiments/README.md) | 最近的完整覆盖、成组筛选和调度实验 |
|
||||||
|
| [`show/`](show/README.md) | 分步骤教学代码、博客/视频稿及 Manim 工程 |
|
||||||
|
| [`exp/`](exp/README.md) | 独立 SMT 逆向搜索探索 |
|
||||||
|
| [`tools/`](tools/README.md) | 单区块检查小工具与历史汇编参考 |
|
||||||
|
| `slime.cpp`、`slime.cu` | 最初的暴力扫描实现,保留原入口 |
|
||||||
|
|
||||||
|
研究现状见 [优化汇总](research/OPTIMIZATION_SUMMARY.md)。历史报告是当次源码与硬件下的记录,不应自动视为当前 CLI 性能。
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make help # 查看构建入口
|
||||||
|
make bench # 当前 CLI
|
||||||
|
make test # CLI 范围/数学/参数检查:需要 nvcc,不需要 GPU
|
||||||
|
make test-gpu # CLI GPU 集成验证
|
||||||
|
make legacy # 最初 CPU/CUDA 程序;单独 make 仍保持这个行为
|
||||||
|
make tools # 单区块检查工具
|
||||||
|
```
|
||||||
|
|
||||||
|
`make research` 和 `make show` 构建相应研究/教学程序。`make clean` 只清理各入口的编译产物,不删除实验数据或报告。
|
||||||
|
|
||||||
|
## 哪些文件应进入 Git
|
||||||
|
|
||||||
|
- 保留源代码、构建脚本、文档、依赖锁文件,以及报告引用的基准 JSON/JSONL 和确定性输入数据;部分复现脚本直接读取这些文件。
|
||||||
|
- 忽略可执行文件、对象文件、`bin/`、虚拟环境、Python 缓存、渲染输出、临时日志和本机配置。
|
||||||
|
- 临时跑分或个人导出可放在根目录 `artifacts/`,整个目录已忽略。不要把 `.gitignore` 扩大成忽略全部 JSON/TXT/汇编,否则会漏掉复现材料。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p artifacts
|
||||||
|
./bench/bin/slime-bench --radius 64 --size 5 --output artifacts/local.json
|
||||||
|
```
|
||||||
|
|
||||||
|
历史研究和教学跑分脚本部分会直接更新各自的 `results/` 与报告,运行它们后出现 Git 修改是预期行为;日常机器测速请使用上述 CLI 和 `artifacts/`。将新结果作为正式基准保存时,应连同硬件、命令、源码版本和结论一起审核提交。
|
||||||
|
|
||||||
|
旧研究头文件路径保留为兼容转发;修改算法时请修改 `include/slime/` 中的实现。旧报告里的源文件哈希保留原测量值,后续运行的实验脚本会额外记录新共用头文件的哈希。
|
||||||
2
bench/.gitignore
vendored
Normal file
2
bench/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/bin/
|
||||||
|
/__pycache__/
|
||||||
21
bench/Makefile
Normal file
21
bench/Makefile
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
NVCC ?= nvcc
|
||||||
|
CUDA_ARCH ?= 86
|
||||||
|
NVCCFLAGS ?= -O3 -std=c++17
|
||||||
|
TARGET := bin/slime-bench
|
||||||
|
HEADERS := ../include/slime/solver_core.hpp ../include/slime/upper_interval.hpp
|
||||||
|
|
||||||
|
.PHONY: all clean test test-gpu
|
||||||
|
all: $(TARGET)
|
||||||
|
$(TARGET): slime_bench.cu $(HEADERS) Makefile
|
||||||
|
mkdir -p bin
|
||||||
|
$(NVCC) $(NVCCFLAGS) -Xcompiler -pthread -gencode arch=compute_$(CUDA_ARCH),code=sm_$(CUDA_ARCH) -gencode arch=compute_$(CUDA_ARCH),code=compute_$(CUDA_ARCH) slime_bench.cu -o $@
|
||||||
|
bin/test-math: test_math.cu slime_bench.cu $(HEADERS) Makefile
|
||||||
|
mkdir -p bin
|
||||||
|
$(NVCC) $(NVCCFLAGS) -Xcompiler -pthread test_math.cu -o $@
|
||||||
|
test: $(TARGET) bin/test-math
|
||||||
|
bin/test-math
|
||||||
|
python3 test_cli.py
|
||||||
|
test-gpu: $(TARGET)
|
||||||
|
python3 test_cli.py --gpu
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET) bin/test-math
|
||||||
170
bench/README.md
Normal file
170
bench/README.md
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
# slime-bench:普通分支全种子搜索基准
|
||||||
|
|
||||||
|
指定中心区块、曼哈顿半径和自定义图案(或实心 `n×n`),搜索范围内每个锚点的全部有效种子,输出 JSON 结果及计时。支持 NVIDIA CUDA 单卡、多卡。
|
||||||
|
|
||||||
|
## 本地编译与运行
|
||||||
|
|
||||||
|
需要 Linux/WSL、CUDA Toolkit(nvcc,推荐 12 或以上)、支持 C++17 的主机编译器。运行时需要 NVIDIA 驱动。项目根目录执行:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make -C bench
|
||||||
|
mkdir -p artifacts
|
||||||
|
./bench/bin/slime-bench --list-devices
|
||||||
|
./bench/bin/slime-bench --x 0 --z 0 --radius 64 --size 5 \
|
||||||
|
--devices 0 --repeat 3 --output artifacts/local-r64-n5.json
|
||||||
|
```
|
||||||
|
|
||||||
|
默认编译 `sm_86` 并附带 PTX,适合本机 RTX 3060 Laptop。换架构时用 `-B` 强制重建;仅改变 Make 变量不会使已有文件自动过期。
|
||||||
|
|
||||||
|
## 已验证实例:5×5 爱心
|
||||||
|
|
||||||
|
种子 **`22428888508636`**,左上角区块 **`(-5,-147)`**(方块原点 `(-80,-2352)`)。每格一个区块,向右为 `+X`、向下为 `+Z`;16 格 `#` 必须是史莱姆,9 格 `.` 必须不是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.#.#.
|
||||||
|
#####
|
||||||
|
#####
|
||||||
|
.###.
|
||||||
|
..#..
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bench/bin/slime-bench --radius 256 \
|
||||||
|
--pattern-file bench/patterns/heart-5x5.txt \
|
||||||
|
--position-batch 256 --max-results 1000
|
||||||
|
|
||||||
|
# 单点复查,收集全部普通分支匹配:应得到 1,181 个种子
|
||||||
|
./bench/bin/slime-bench --x -5 --z -147 --radius 0 \
|
||||||
|
--pattern-file bench/patterns/heart-5x5.txt --max-results 2000
|
||||||
|
```
|
||||||
|
|
||||||
|
RTX 3060 Laptop 实测:131,585 个锚点,普通分支共找到 1,456 个种子/位置匹配,扫描约 2.439 秒(含设备初始化的端到端约 2.620 秒)。保存的 1,000 个搜索样本,以及单点复查的全部 1,181 个种子,都通过独立 Python 实现的 Java 整数回绕及 `Random.nextInt(10)` 精确逐格验证。已知种子及计时见 [results/heart-5x5-example.json](results/heart-5x5-example.json)。GPU 回归检查单点计数、已知种子及全部样本,搜索计数不包含拒绝重抽补扫。
|
||||||
|
|
||||||
|
## 自定义图案:6×6 苦力怕
|
||||||
|
|
||||||
|
`--pattern` 接受 `/` 分隔的行,或用 `--pattern-file` 读取每行一排的文本文件。`#` 必须是史莱姆区块,`.` 必须不是,`?` 不限制。支持非正方形,宽、高分别为 1~64,至少含一个 `#`;不能与 `--size` 同时指定。不自动旋转或镜像。
|
||||||
|
|
||||||
|
内置示例 [patterns/creeper-6x6.txt](patterns/creeper-6x6.txt) 将脸部像素设为史莱姆:
|
||||||
|
|
||||||
|
```text
|
||||||
|
##..##
|
||||||
|
##..##
|
||||||
|
..##..
|
||||||
|
.####.
|
||||||
|
.####.
|
||||||
|
.#..#.
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bench/bin/slime-bench --radius 512 \
|
||||||
|
--pattern-file bench/patterns/creeper-6x6.txt \
|
||||||
|
--position-batch 128 --max-results 100 --output artifacts/creeper.json
|
||||||
|
|
||||||
|
# 也可直接输入;必须使用引号保护 #、? 等字符
|
||||||
|
./bench/bin/slime-bench --radius 512 \
|
||||||
|
--pattern '##..##/##..##/..##../.####./.####./.#..#.' --max-results 100
|
||||||
|
```
|
||||||
|
|
||||||
|
目标是上面的正色模板:眼睛、鼻子和嘴巴为史莱姆区块,背景必须不是。已检查原点半径 2048、中心 `(51069,419124)` 半径 1024、32 个分散区域各半径 256、另外 128 个分散区域各半径 512;还筛选了 65,536 个坐标,选出低位候选最多的 64 个坐标类,检查这些类在世界边界内的全部 104,538 个实际位置。累计 82,047,228 次锚点检查仍未命中(不同路线可能重叠,见[搜索记录](results/creeper-6x6-search.json))。这不证明正色不存在;不能用反色图案作为该目标的搜索结果。
|
||||||
|
|
||||||
|
每行从左到右沿 `+X`,从上到下沿 `+Z`。返回坐标仍是矩形左上角,即使该格是 `.` 或 `?`。将 `.` 改为 `?` 可只要求轮廓、允许背景出现额外史莱姆区块。JSON 包含原始 `pattern` 行、宽高及两类约束数;自定义输入的 `square_size` 为 `null`。
|
||||||
|
|
||||||
|
筛选和区间求解只使用 `#` 格,随后精确核对所有 `.` 格;`?` 不参与计算。稀疏图案的候选可能很多,搜索时间不能直接套用实心方阵的基准。搜索口径仍为普通分支,不包含拒绝重抽补扫。
|
||||||
|
|
||||||
|
## H100 服务器
|
||||||
|
|
||||||
|
将仓库内的 `bench/` 和 `include/` 一起复制到服务器,保持相对路径;CLI 依赖 `include/slime/` 中的共用头文件。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make -C bench -B CUDA_ARCH=90
|
||||||
|
mkdir -p artifacts
|
||||||
|
./bench/bin/slime-bench --list-devices
|
||||||
|
|
||||||
|
# 先测单张 H100,和本地使用相同范围、尺寸及批大小。
|
||||||
|
./bench/bin/slime-bench --x 0 --z 0 --radius 64 --size 5 \
|
||||||
|
--devices 0 --repeat 3 --output artifacts/h100-one-r64-n5.json
|
||||||
|
|
||||||
|
# 再测全部可见 GPU;八卡机器即可使用八张卡。
|
||||||
|
./bench/bin/slime-bench --x 0 --z 0 --radius 64 --size 5 \
|
||||||
|
--devices all --repeat 3 --output artifacts/h100-all-r64-n5.json
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以指定 `--devices 0,1,2,3,4,5,6,7`。编号遵循 `CUDA_VISIBLE_DEVICES` 的可见编号。重复/不存在的设备编号会报错。多卡以动态位置批次分配任务,完整覆盖范围一次,各卡独立缓存类根,结果总数合并;所有 GPU 初始化完成后一起开始计时。实际八卡性能尚未在本机验证。
|
||||||
|
|
||||||
|
半径 64 包含 8,321 个锚点,适合先验证环境。正式比较可以在两台机器上使用下面同一套较长参数(半径 512,共 525,313 个锚点):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bench/bin/slime-bench --x 0 --z 0 --radius 512 --size 5 \
|
||||||
|
--position-batch 128 --devices all --repeat 3 --output artifacts/benchmark-r512-n5.json
|
||||||
|
```
|
||||||
|
|
||||||
|
若八卡上仍耗时太短,可同时增大半径。`--repeat 3` 是完整扫描三遍,不是只跑三次 kernel。默认无额外隐藏预热;第一轮包含首次 kernel/JIT 开销,报告保留每一轮数据。
|
||||||
|
|
||||||
|
## 范围与搜索口径
|
||||||
|
|
||||||
|
```text
|
||||||
|
abs(anchor_x - x) + abs(anchor_z - z) <= radius
|
||||||
|
锚点数量 = 1 + 2*radius*(radius+1)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `x,z,radius` 的单位都是区块,不是方块。
|
||||||
|
- 锚点是图案最小 X、最小 Z 的角;图案沿 `+X,+Z` 延伸。自定义图案的锚点是外接矩形的角。
|
||||||
|
- 半径限制锚点,图案允许超出菱形范围。
|
||||||
|
- 不自动按世界边界裁剪;锚点和整块图案须在 int32 坐标范围内,否则报错。
|
||||||
|
- `--size` 支持 1~6,默认 5。小尺寸匹配种子极多,全种子计数也可能耗时很久,不能用 1×1 推断 5×5 性能。
|
||||||
|
- 遍历所有有效低 48 位种子的**普通候选分支**。沿用低位筛选、16 候选组 mask、区间求交、精确 Java 验证,**不包含拒绝重抽补扫**。
|
||||||
|
- 返回的种子是 `[0,2^48)` 内的有效代表;不重复枚举高 16 位等价的 64 位世界种子。
|
||||||
|
|
||||||
|
这是范围基准 CLI,不提供全世界长跑的断点续跑。Ctrl+C 可终止,未完成扫描不会输出成功报告。
|
||||||
|
|
||||||
|
## 参数及输出
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bench/bin/slime-bench --help
|
||||||
|
|
||||||
|
# 不使用 GPU,只检查输入范围
|
||||||
|
./bench/bin/slime-bench --x 51069 --z 419124 --radius 2 --size 5 --dry-run
|
||||||
|
|
||||||
|
# 已知位置验证:应该得到 24 个种子
|
||||||
|
./bench/bin/slime-bench --x 51069 --z 419124 --radius 0 --size 5 \
|
||||||
|
--max-results 100 --repeat 3 --output artifacts/known.json
|
||||||
|
```
|
||||||
|
|
||||||
|
本文示例均从项目根目录运行,输出前请先执行 `mkdir -p artifacts`。日常跑分写入已忽略的 `artifacts/`;`results/` 只保存需要长期引用的基准样例。
|
||||||
|
|
||||||
|
JSON 输出到 stdout,状态/约每 5 秒进度输出到 stderr;`--output` 同时保存 JSON,已有文件会报错以免覆盖。默认只计数;`--max-results N` 最多保留 N 个示例三元组 `[seed48,chunk_x,chunk_z]`,**不提前停止扫描**。示例顺序受 GPU 调度影响,不能按示例子集或其顺序对比性能/正确性。`sample_truncated` 表示有命中没有保存在示例中,`matches` 始终是完整计数;整数计数溢出会失败。
|
||||||
|
|
||||||
|
主要计时字段:
|
||||||
|
|
||||||
|
| 字段 | 含义 |
|
||||||
|
|---|---|
|
||||||
|
| `rounds[].elapsed_ms` | 本轮启动工作线程、设备初始化/分配、完整扫描及资源清理的墙钟时间 |
|
||||||
|
| `rounds[].scan_wall_ms` | 所有 GPU 就绪后,从最早开始扫描到最后完成的墙钟时间,含本轮分组、根生成、传输、展开、搜索、结果回传 |
|
||||||
|
| `median_scan_wall_ms` | 各轮 `scan_wall_ms` 的中位数,适合本地与服务器比较 |
|
||||||
|
| `rounds[].anchors_per_second` | 实际锚点数 / 扫描墙钟秒数 |
|
||||||
|
| `gpus[].root_stage_ms` | 该 GPU 工作线程的代表类构造、根筛选、回传、整理及缓存耗时 |
|
||||||
|
| `gpus[].search_stage_ms` | 该线程的根任务上传、公共项准备、成组搜索和命中回传耗时 |
|
||||||
|
| `gpus[].scan_ms` | 该线程完整扫描耗时;还包含以上两阶段外的调度、具体位置构造等工作 |
|
||||||
|
|
||||||
|
多卡阶段时间相互重叠,不要把八卡 `scan_ms` 相加当作完成时间。比较相同的中心、半径、尺寸和批大小;位置根分布差异很大,单个已知命中点不适合代表全世界平均吞吐。
|
||||||
|
|
||||||
|
可调批参数:
|
||||||
|
|
||||||
|
- `--position-batch 32`:每个动态任务最多 32 个锚点,支持 1~256。
|
||||||
|
- `--job-batch 2048`:每次搜索最多 2048 个低位根任务,支持 1~65536。
|
||||||
|
- `--cache-classes 4096`:每卡缓存的坐标类数量,0 禁用;另限制最多约 1,048,576 个缓存根,FIFO 淘汰。每轮清空缓存,避免重复测量只跑缓存命中。
|
||||||
|
|
||||||
|
按坐标类归一化并只构造有根类的具体位置。此 CLI 使用有限缓存、按曼哈顿行顺序调度;不是此前设想的全世界按类遍历计划。小半径下通常没有重复类,所以测到的根生成费用是真实费用,不会按假设将其省略。半径范围的实测吞吐不能直接替代此前约 50 天的全世界类复用搜索阶段外推。
|
||||||
|
|
||||||
|
## 检查
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make -C bench test # 无需 GPU:范围、输入、可变尺寸数学对照
|
||||||
|
make -C bench test-gpu # 需要 GPU:实际搜索与文件输出验证
|
||||||
|
```
|
||||||
|
|
||||||
|
验证内容包括:自定义图案解析和边界、稀疏图案高位枚举对照、带忽略格及非史莱姆格的 GPU 结果与独立 Java 算法筛选对照,以及半径 0~64 的完整菱形枚举,较大半径抽样,1~6 中多个尺寸的组 mask 对照,已知 24 个命中,小根批分拆一致性,4×4 的全部 422 个示例经独立 Java 算法验证,6×6 的已知种子子集对照,以及半径 2 批量结果与全部 13 个位置分别搜索的完整列表一致。
|
||||||
|
|
||||||
|
本机实测:RTX 3060 Laptop,中心 `(0,0)`,半径 512,5×5,`--position-batch 128 --repeat 3`。525,313 个锚点,三轮均有 368,935 个低位根任务、0 个命中,扫描墙钟中位数约 **3.990 秒**。原始 JSON 在 [results/local-r512-n5.json](results/local-r512-n5.json)。另有半径 64 的快速环境验证记录;其批大小为默认 32,与上述正式样例不同。
|
||||||
|
|
||||||
|
H100 的 `sm_90` 原生代码和 PTX 已在本机交叉编译通过,尚未在 H100 或八张物理 GPU 上执行。
|
||||||
6
bench/patterns/creeper-6x6.txt
Normal file
6
bench/patterns/creeper-6x6.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
##..##
|
||||||
|
##..##
|
||||||
|
..##..
|
||||||
|
.####.
|
||||||
|
.####.
|
||||||
|
.#..#.
|
||||||
5
bench/patterns/heart-5x5.txt
Normal file
5
bench/patterns/heart-5x5.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.#.#.
|
||||||
|
#####
|
||||||
|
#####
|
||||||
|
.###.
|
||||||
|
..#..
|
||||||
2076
bench/results/creeper-6x6-search.json
Normal file
2076
bench/results/creeper-6x6-search.json
Normal file
File diff suppressed because it is too large
Load diff
52
bench/results/heart-5x5-example.json
Normal file
52
bench/results/heart-5x5-example.json
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"pattern": [
|
||||||
|
".#.#.",
|
||||||
|
"#####",
|
||||||
|
"#####",
|
||||||
|
".###.",
|
||||||
|
"..#.."
|
||||||
|
],
|
||||||
|
"seed48": 22428888508636,
|
||||||
|
"chunk_x": -5,
|
||||||
|
"chunk_z": -147,
|
||||||
|
"block_origin": [
|
||||||
|
-80,
|
||||||
|
-2352
|
||||||
|
],
|
||||||
|
"verified_rows": [
|
||||||
|
".#.#.",
|
||||||
|
"#####",
|
||||||
|
"#####",
|
||||||
|
".###.",
|
||||||
|
"..#.."
|
||||||
|
],
|
||||||
|
"verification": {
|
||||||
|
"method": "independent Python implementation of Java int overflow and Random.nextInt(10) rejection loop",
|
||||||
|
"verified_search_sample_count": 1000,
|
||||||
|
"verified_single_anchor_count": 1181,
|
||||||
|
"slime_cells": 16,
|
||||||
|
"non_slime_cells": 9
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"center": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"radius_chunks": 256,
|
||||||
|
"anchors": 131585,
|
||||||
|
"matches": 1456,
|
||||||
|
"scan_wall_ms": 2438.534811,
|
||||||
|
"elapsed_ms": 2619.994137,
|
||||||
|
"gpu": "NVIDIA GeForce RTX 3060 Laptop GPU",
|
||||||
|
"rejection_tail_included": false
|
||||||
|
},
|
||||||
|
"single_anchor": {
|
||||||
|
"matches": 1181,
|
||||||
|
"root_jobs": 405
|
||||||
|
},
|
||||||
|
"source_sha256": {
|
||||||
|
"bench/slime_bench.cu": "3c70cbe2e5babc3e737199fb5e62b78ea9c5daadc0020af77292c2d59516259e",
|
||||||
|
"include/slime/solver_core.hpp": "feb1200518716b4d796fb60f60a83b6b798155bbd4e795531793181a5c7f9eba",
|
||||||
|
"include/slime/upper_interval.hpp": "af3fddf7705295d91ffd6e2190f542004a3ecff847fce9d4c9b13125bdbdc2d5"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
bench/results/local-r512-n5.json
Normal file
1
bench/results/local-r512-n5.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"schema_version":1,"mode":"ordinary_all_effective_seeds","rejection_tail_included":false,"center":[0,0],"radius_chunks":512,"square_size":5,"anchors":525313,"effective_seed_bits":48,"dry_run":false,"position_batch":128,"job_batch":2048,"cache_classes":4096,"cuda_runtime_version":12000,"rounds":[{"round":1,"elapsed_ms":4145.453479,"scan_wall_ms":3973.099405,"anchors_per_second":132217.431897,"root_jobs":368935,"matches":0,"sample_truncated":false,"sample":[],"gpus":[{"device":0,"name":"NVIDIA GeForce RTX 3060 Laptop GPU","anchors":525313,"root_classes_generated":525313,"cache_hits":0,"root_jobs":368935,"matches":0,"setup_ms":170.953854,"scan_ms":3973.099405,"root_stage_ms":3429.569304,"search_stage_ms":369.153727}]},{"round":2,"elapsed_ms":3991.578504,"scan_wall_ms":3989.615391,"anchors_per_second":131670.085589,"root_jobs":368935,"matches":0,"sample_truncated":false,"sample":[],"gpus":[{"device":0,"name":"NVIDIA GeForce RTX 3060 Laptop GPU","anchors":525313,"root_classes_generated":525313,"cache_hits":0,"root_jobs":368935,"matches":0,"setup_ms":0.879198,"scan_ms":3989.615391,"root_stage_ms":3441.271225,"search_stage_ms":371.001978}]},{"round":3,"elapsed_ms":4101.32096,"scan_wall_ms":4099.380455,"anchors_per_second":128144.485677,"root_jobs":368935,"matches":0,"sample_truncated":false,"sample":[],"gpus":[{"device":0,"name":"NVIDIA GeForce RTX 3060 Laptop GPU","anchors":525313,"root_classes_generated":525313,"cache_hits":0,"root_jobs":368935,"matches":0,"setup_ms":0.86543,"scan_ms":4099.380455,"root_stage_ms":3543.591974,"search_stage_ms":374.196725}]}],"median_scan_wall_ms":3989.615391}
|
||||||
1
bench/results/local-r64-n5.json
Normal file
1
bench/results/local-r64-n5.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"schema_version":1,"mode":"ordinary_all_effective_seeds","rejection_tail_included":false,"center":[0,0],"radius_chunks":64,"square_size":5,"anchors":8321,"effective_seed_bits":48,"dry_run":false,"position_batch":32,"job_batch":2048,"cache_classes":4096,"cuda_runtime_version":12000,"rounds":[{"round":1,"elapsed_ms":284.341397,"scan_wall_ms":95.570636,"anchors_per_second":87066.4918459,"root_jobs":6835,"matches":0,"sample_truncated":false,"sample":[],"gpus":[{"device":0,"name":"NVIDIA GeForce RTX 3060 Laptop GPU","anchors":8321,"root_classes_generated":8321,"cache_hits":0,"root_jobs":6835,"matches":0,"setup_ms":187.64489,"scan_ms":95.570636,"root_stage_ms":81.438496,"search_stage_ms":9.997073}]},{"round":2,"elapsed_ms":95.692767,"scan_wall_ms":94.367954,"anchors_per_second":88176.1196179,"root_jobs":6835,"matches":0,"sample_truncated":false,"sample":[],"gpus":[{"device":0,"name":"NVIDIA GeForce RTX 3060 Laptop GPU","anchors":8321,"root_classes_generated":8321,"cache_hits":0,"root_jobs":6835,"matches":0,"setup_ms":0.577818,"scan_ms":94.367954,"root_stage_ms":80.619006,"search_stage_ms":9.56154}]},{"round":3,"elapsed_ms":88.980653,"scan_wall_ms":87.740238,"anchors_per_second":94836.7612133,"root_jobs":6835,"matches":0,"sample_truncated":false,"sample":[],"gpus":[{"device":0,"name":"NVIDIA GeForce RTX 3060 Laptop GPU","anchors":8321,"root_classes_generated":8321,"cache_hits":0,"root_jobs":6835,"matches":0,"setup_ms":0.466152,"scan_ms":87.740238,"root_stage_ms":74.091536,"search_stage_ms":9.67283}]}],"median_scan_wall_ms":94.367954}
|
||||||
235
bench/slime_bench.cu
Normal file
235
bench/slime_bench.cu
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
#include "../include/slime/upper_interval.hpp"
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
namespace sr=slime_research;
|
||||||
|
namespace fs=full_search_experiment;
|
||||||
|
using Clock=std::chrono::steady_clock;
|
||||||
|
#define CUDA(call) do{cudaError_t status_=(call);if(status_!=cudaSuccess)throw std::runtime_error(std::string(#call)+": "+cudaGetErrorString(status_));}while(0)
|
||||||
|
struct Coord{int32_t x,z;};
|
||||||
|
struct Job{uint32_t position,root;};
|
||||||
|
struct Match{uint64_t seed;int32_t x,z;};
|
||||||
|
struct PatternCell{int x,z;uint8_t requirement;};
|
||||||
|
struct Pattern{int width=0,height=0,positive=0;std::vector<std::string> rows;std::vector<PatternCell> cells;};
|
||||||
|
Pattern parsePattern(std::string text){
|
||||||
|
std::replace(text.begin(),text.end(),'/','\n');
|
||||||
|
Pattern p;std::stringstream input(text);std::string row;
|
||||||
|
while(std::getline(input,row)){if(!row.empty()&&row.back()=='\r')row.pop_back();if(row.empty())throw std::runtime_error("empty pattern row");p.rows.push_back(row);}
|
||||||
|
if(p.rows.empty())throw std::runtime_error("empty pattern");
|
||||||
|
p.width=p.rows[0].size();p.height=p.rows.size();
|
||||||
|
if(p.width>64||p.height>64)throw std::runtime_error("pattern dimensions must be 1..64");
|
||||||
|
for(auto& r:p.rows)if(r.size()!=size_t(p.width))throw std::runtime_error("pattern rows must have equal width");
|
||||||
|
for(char wanted:{'#','.'})for(int z=0;z<p.height;++z)for(int x=0;x<p.width;++x){char ch=p.rows[z][x];
|
||||||
|
if(ch!='#'&&ch!='.'&&ch!='?')throw std::runtime_error("pattern characters must be #, . or ?");
|
||||||
|
if(ch==wanted)p.cells.push_back({x,z,uint8_t(ch=='#'?sr::kSlime:sr::kNonSlime)});
|
||||||
|
if(wanted=='#'&&ch=='#')++p.positive;
|
||||||
|
}
|
||||||
|
if(!p.positive)throw std::runtime_error("pattern requires at least one # cell");
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
struct Config{Pattern pattern;int32_t x=0,z=0;uint64_t radius=0;int size=5,positionBatch=32,jobBatch=2048,repeats=1;uint64_t maxResults=0;size_t cacheClasses=4096;std::string devices="0",output;bool dry=false,list=false;};
|
||||||
|
uint64_t number(const std::string& s){size_t used=0;if(s.empty()||s[0]=='-')throw std::runtime_error("expected nonnegative integer: "+s);auto n=std::stoull(s,&used);if(used!=s.size())throw std::runtime_error("invalid integer: "+s);return n;}
|
||||||
|
int32_t signed32(const std::string&s){size_t used=0;auto n=std::stoll(s,&used);if(used!=s.size()||n<INT32_MIN||n>INT32_MAX)throw std::runtime_error("invalid int32 coordinate");return int32_t(n);}
|
||||||
|
uint64_t anchors(uint64_t r){return 1+2*r*(r+1);}
|
||||||
|
uint64_t isqrt(uint64_t v){uint64_t r=std::sqrt(double(v));while((r+1)*(r+1)<=v)++r;while(r*r>v)--r;return r;}
|
||||||
|
Coord coordinate(const Config& c,uint64_t index){
|
||||||
|
uint64_t top=(c.radius+1)*(c.radius+1);int64_t dx,dz;
|
||||||
|
if(index<top){uint64_t row=isqrt(index);dz=int64_t(row)-c.radius;dx=int64_t(index-row*row)-int64_t(row);}
|
||||||
|
else{uint64_t back=anchors(c.radius)-1-index,row=isqrt(back);dz=int64_t(c.radius)-row;dx=int64_t(row)-int64_t(back-row*row);}
|
||||||
|
return {int32_t(int64_t(c.x)+dx),int32_t(int64_t(c.z)+dz)};
|
||||||
|
}
|
||||||
|
uint64_t classKey(Coord p){return (uint64_t(uint32_t(p.x)&65535)<<17)|(uint32_t(p.z)&131071);}
|
||||||
|
void terms(Coord p,int n,sr::CellTerm* out){uint64_t anchor=sr::coordinateTerm(p.x,p.z);for(int z=0;z<n;++z)for(int x=0;x<n;++x)out[z*n+x]={(sr::coordinateTerm(p.x+x,p.z+z)-anchor)&sr::kMask48,sr::kSlime};}
|
||||||
|
void terms(Coord p,const Pattern& pattern,sr::CellTerm* out){
|
||||||
|
// The grouped filter needs its first positive term to be zero.
|
||||||
|
auto first=pattern.cells.front();uint64_t anchor=sr::coordinateTerm(p.x+first.x,p.z+first.z);size_t i=0;
|
||||||
|
for(auto cell:pattern.cells)out[i++]={(sr::coordinateTerm(p.x+cell.x,p.z+cell.z)-anchor)&sr::kMask48,cell.requirement};
|
||||||
|
}
|
||||||
|
__global__ void rootsKernel(const sr::CellTerm* cells,uint32_t positions,int count,int positive,Job* out,unsigned long long* total){
|
||||||
|
uint64_t t=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;if(t>=(uint64_t(positions)<<18))return;
|
||||||
|
uint32_t pos=t>>18,root=t&262143;const auto* p=cells+uint64_t(pos)*count;
|
||||||
|
for(int i=0;i<positive;++i)if((((root+uint32_t(p[i].term))^uint32_t(sr::kCombinedXor))*uint32_t(sr::kJavaMultiplier)+11U)&(1U<<17))return;
|
||||||
|
out[atomicAdd(total,1ULL)]={pos,root};
|
||||||
|
}
|
||||||
|
__global__ void prepareKernel(const sr::CellTerm* cells,const Job* jobs,uint32_t n,int count,int positive,uint32_t* prepared){
|
||||||
|
uint32_t k=blockIdx.x*blockDim.x+threadIdx.x;if(k>=n)return;const auto job=jobs[k];
|
||||||
|
for(int i=0;i<positive;++i){uint64_t a=job.root+cells[uint64_t(job.position)*count+i].term;
|
||||||
|
prepared[(uint64_t(k)*count+i)*2]=a>>18;
|
||||||
|
prepared[(uint64_t(k)*count+i)*2+1]=((((a^sr::kCombinedXor)&262143)*sr::kJavaMultiplier+11)>>17);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__host__ __device__ __forceinline__ uint32_t q(const uint32_t*p,int i,uint32_t m){return (p[2*i+1]+2U*uint32_t(sr::kJavaMultiplier)*((m+p[2*i])^uint32_t(sr::kCombinedXor>>18)))&0x7fffffffU;}
|
||||||
|
__host__ __device__ uint32_t groupMask(const uint32_t*p,uint32_t base,int count){
|
||||||
|
uint32_t mask=65535,q0=q(p,0,base),qe=q(p,0,base+240);
|
||||||
|
for(int i=1;i<count&&mask;++i){uint32_t cut=(256-((base+p[2*i])&255)+15)>>4,lo=(1U<<cut)-1;
|
||||||
|
uint32_t r0=((q(p,i,base)-q0)&0x7fffffffU)%10,r1=((q(p,i,base+240)-qe)&0x7fffffffU)%10;
|
||||||
|
mask&=((r0==0||r0==8)?lo:0U)|((r1==0||r1==8)?(65535U^lo):0U);
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
struct Emit{
|
||||||
|
Coord p;uint64_t originTerm;const sr::CellTerm* negative;int negativeCount;Match* out;uint64_t capacity;unsigned long long* total;
|
||||||
|
__device__ void operator()(uint64_t seed){if(!sr::verifyPattern(seed,negative,negativeCount))return;uint64_t slot=atomicAdd(total,1ULL);if(slot<capacity)out[slot]={(seed-originTerm)&sr::kMask48,p.x,p.z};}
|
||||||
|
};
|
||||||
|
__global__ void searchKernel(const sr::CellTerm*cells,const Coord*coords,const Job*jobs,uint32_t n,int count,int positive,int originX,int originZ,const uint32_t*prepared,Match*out,uint64_t capacity,unsigned long long*total){
|
||||||
|
uint64_t index=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;if(index>=(uint64_t(n)<<13))return;
|
||||||
|
uint32_t g=index&8191,k=index>>13,base=((g>>4)<<8)+(g&15);const uint32_t* p=prepared+uint64_t(k)*count*2;
|
||||||
|
uint32_t mask=groupMask(p,base,positive);
|
||||||
|
Job job=jobs[k];Coord origin=coords[job.position];Emit emit{origin,sr::coordinateTerm(origin.x+originX,origin.z+originZ),cells+uint64_t(job.position)*count+positive,count-positive,out,capacity,total};
|
||||||
|
while(mask){uint32_t j=__ffs(mask)-1;mask&=mask-1;fs::solveIntervals(job.root+(uint64_t(base+16*j)<<18),cells+uint64_t(job.position)*count,positive,emit);}
|
||||||
|
}
|
||||||
|
template<class T>struct Buffer{T* p=nullptr;explicit Buffer(size_t n){if(n)CUDA(cudaMalloc(&p,n*sizeof(T)));}~Buffer(){if(p)cudaFree(p);}Buffer(const Buffer&)=delete;};
|
||||||
|
struct Metrics{int device;std::string name;uint64_t positions=0,classes=0,cacheHits=0,jobs=0,matches=0;double setupMs=0,scanMs=0,rootMs=0,searchMs=0;Clock::time_point start,end;std::vector<Match> sample;};
|
||||||
|
struct Shared{std::atomic<uint64_t> next{0},done{0};std::atomic<int> finished{0};int ready=0,workerCount=0;std::condition_variable cv;std::atomic<bool> cancel{false};std::mutex mutex;std::string error;};
|
||||||
|
void worker(const Config& c,int device,Shared& shared,Metrics& result){try{
|
||||||
|
auto setup=Clock::now();CUDA(cudaSetDevice(device));cudaDeviceProp prop{};CUDA(cudaGetDeviceProperties(&prop,device));result.device=device;result.name=prop.name;
|
||||||
|
int count=c.pattern.cells.size(),positive=c.pattern.positive;size_t rootsCapacity=size_t(c.positionBatch)*(1U<<17);
|
||||||
|
Buffer<sr::CellTerm> dcells(size_t(c.positionBatch)*count);Buffer<Coord>dcoords(c.positionBatch);Buffer<Job>droots(rootsCapacity),djobs(c.jobBatch);
|
||||||
|
Buffer<uint32_t>prep(size_t(c.jobBatch)*count*2);Buffer<unsigned long long>total(1);Buffer<Match>matches(c.maxResults);
|
||||||
|
std::map<uint64_t,std::vector<uint32_t>> cache;std::deque<uint64_t> fifo;size_t cachedRoots=0;
|
||||||
|
result.setupMs=std::chrono::duration<double,std::milli>(Clock::now()-setup).count();{
|
||||||
|
std::unique_lock<std::mutex> lock(shared.mutex);++shared.ready;shared.cv.notify_all();
|
||||||
|
shared.cv.wait(lock,[&](){return shared.ready==shared.workerCount||shared.cancel.load();});
|
||||||
|
if(shared.cancel)return;
|
||||||
|
}
|
||||||
|
result.start=Clock::now();
|
||||||
|
const uint64_t end=anchors(c.radius);
|
||||||
|
while(!shared.cancel){uint64_t first=shared.next.fetch_add(c.positionBatch);if(first>=end)break;
|
||||||
|
uint32_t num=std::min<uint64_t>(c.positionBatch,end-first);std::vector<Coord> coords;std::vector<uint64_t> keys;
|
||||||
|
std::map<uint64_t,std::vector<uint32_t>> lists;std::vector<Coord> missing;std::vector<uint64_t> missingKeys;
|
||||||
|
for(uint32_t i=0;i<num;++i){Coord p=coordinate(c,first+i);coords.push_back(p);uint64_t key=classKey(p);keys.push_back(key);
|
||||||
|
if(lists.count(key)){++result.cacheHits;continue;}auto it=cache.find(key);
|
||||||
|
if(it!=cache.end()){lists[key]=it->second;++result.cacheHits;}else{lists[key]={};missing.push_back(p);missingKeys.push_back(key);}
|
||||||
|
}
|
||||||
|
if(!missing.empty()){
|
||||||
|
auto start=Clock::now();std::vector<sr::CellTerm> cells(missing.size()*count);for(size_t i=0;i<missing.size();++i)terms(missing[i],c.pattern,cells.data()+i*count);
|
||||||
|
CUDA(cudaMemcpy(dcells.p,cells.data(),cells.size()*sizeof(sr::CellTerm),cudaMemcpyHostToDevice));CUDA(cudaMemset(total.p,0,sizeof(*total.p)));
|
||||||
|
rootsKernel<<<unsigned((uint64_t(missing.size())<<18)/256),256>>>(dcells.p,missing.size(),count,positive,droots.p,total.p);CUDA(cudaGetLastError());
|
||||||
|
unsigned long long found;CUDA(cudaMemcpy(&found,total.p,sizeof(found),cudaMemcpyDeviceToHost));if(found>rootsCapacity)throw std::runtime_error("root capacity invariant failed");
|
||||||
|
std::vector<Job> roots(found);if(found)CUDA(cudaMemcpy(roots.data(),droots.p,found*sizeof(Job),cudaMemcpyDeviceToHost));
|
||||||
|
for(auto job:roots)lists[missingKeys[job.position]].push_back(job.root);
|
||||||
|
result.classes+=missing.size();
|
||||||
|
for(auto key:missingKeys){auto& list=lists[key];std::sort(list.begin(),list.end());if(c.cacheClasses){
|
||||||
|
while(!fifo.empty()&&(cache.size()>=c.cacheClasses||cachedRoots+list.size()>(1U<<20))){auto old=fifo.front();fifo.pop_front();cachedRoots-=cache.at(old).size();cache.erase(old);}
|
||||||
|
cache[key]=list;cachedRoots+=list.size();fifo.push_back(key);
|
||||||
|
}}
|
||||||
|
result.rootMs+=std::chrono::duration<double,std::milli>(Clock::now()-start).count();
|
||||||
|
}
|
||||||
|
// Construct concrete positions only for classes with roots.
|
||||||
|
std::vector<Coord> active;std::vector<sr::CellTerm> cells;std::vector<Job> jobs;jobs.reserve(c.jobBatch);
|
||||||
|
for(uint32_t i=0;i<num;++i)if(!lists.at(keys[i]).empty())active.push_back(coords[i]);
|
||||||
|
if(!active.empty()){
|
||||||
|
cells.resize(active.size()*count);for(size_t i=0;i<active.size();++i)terms(active[i],c.pattern,cells.data()+i*count);
|
||||||
|
CUDA(cudaMemcpy(dcells.p,cells.data(),cells.size()*sizeof(sr::CellTerm),cudaMemcpyHostToDevice));CUDA(cudaMemcpy(dcoords.p,active.data(),active.size()*sizeof(Coord),cudaMemcpyHostToDevice));
|
||||||
|
auto search=[&](){if(jobs.empty()||shared.cancel)return;auto start=Clock::now();
|
||||||
|
CUDA(cudaMemcpy(djobs.p,jobs.data(),jobs.size()*sizeof(Job),cudaMemcpyHostToDevice));CUDA(cudaMemset(total.p,0,sizeof(*total.p)));
|
||||||
|
prepareKernel<<<unsigned((jobs.size()+127)/128),128>>>(dcells.p,djobs.p,jobs.size(),count,positive,prep.p);
|
||||||
|
searchKernel<<<unsigned((uint64_t(jobs.size())<<13)/256),256>>>(dcells.p,dcoords.p,djobs.p,jobs.size(),count,positive,c.pattern.cells.front().x,c.pattern.cells.front().z,prep.p,matches.p,c.maxResults,total.p);CUDA(cudaGetLastError());
|
||||||
|
unsigned long long found;CUDA(cudaMemcpy(&found,total.p,sizeof(found),cudaMemcpyDeviceToHost));if(found>UINT64_MAX-result.matches)throw std::runtime_error("match count exceeds uint64");result.matches+=found;result.jobs+=jobs.size();
|
||||||
|
size_t take=std::min<uint64_t>(found,c.maxResults-result.sample.size());if(take){size_t old=result.sample.size();result.sample.resize(old+take);CUDA(cudaMemcpy(result.sample.data()+old,matches.p,take*sizeof(Match),cudaMemcpyDeviceToHost));}
|
||||||
|
result.searchMs+=std::chrono::duration<double,std::milli>(Clock::now()-start).count();jobs.clear();};
|
||||||
|
for(uint32_t i=0;i<active.size()&&!shared.cancel;++i)for(uint32_t root:lists.at(classKey(active[i]))){jobs.push_back({i,root});if(jobs.size()==size_t(c.jobBatch))search();}
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
result.positions+=num;shared.done.fetch_add(num);
|
||||||
|
}
|
||||||
|
result.end=Clock::now();result.scanMs=std::chrono::duration<double,std::milli>(result.end-result.start).count();
|
||||||
|
}catch(const std::exception& e){shared.cancel=true;std::lock_guard<std::mutex> lock(shared.mutex);shared.error="GPU "+std::to_string(device)+": "+e.what();shared.cv.notify_all();}}
|
||||||
|
std::string escape(const std::string&s){std::ostringstream o;for(unsigned char ch:s){if(ch=='"'||ch=='\\')o<<'\\'<<char(ch);else if(ch<32)o<<"\\u"<<std::hex<<std::setw(4)<<std::setfill('0')<<int(ch)<<std::dec;else o<<char(ch);}return o.str();}
|
||||||
|
void help(){std::cout<<R"(slime-bench --radius R [options]
|
||||||
|
Search effective 48-bit seeds for slime chunk patterns.
|
||||||
|
Ordinary candidate branch only; rejection-tail supplement is NOT included.
|
||||||
|
|
||||||
|
--x X --z Z Center chunk coordinates (default 0,0)
|
||||||
|
--radius R Manhattan radius in chunks (required, >=0)
|
||||||
|
--size N Solid square side, 1..6 (default 5)
|
||||||
|
--pattern TEXT Rows separated by /; # slime, . non-slime, ? ignored
|
||||||
|
--pattern-file FILE Same pattern, one row per line (dimensions 1..64)
|
||||||
|
Requires at least one #; exclusive with --size
|
||||||
|
--devices 0,1,... CUDA device indices, or all (default 0)
|
||||||
|
--repeat N Complete passes; fresh root cache each pass (default 1)
|
||||||
|
--position-batch N Anchors per scheduled batch, 1..256 (default 32)
|
||||||
|
--job-batch N Low18-root jobs per GPU launch, 1..65536 (default 2048)
|
||||||
|
--cache-classes N Per-GPU bounded FIFO root cache entries (default 4096)
|
||||||
|
--max-results N Save up to N example matches; count ALL (default 0)
|
||||||
|
--output FILE Also save the complete JSON report (file must not exist)
|
||||||
|
--dry-run Print scope JSON without using CUDA
|
||||||
|
--list-devices List visible CUDA devices
|
||||||
|
--help Show this help
|
||||||
|
|
||||||
|
Scope: abs(anchor_x-X)+abs(anchor_z-Z)<=R, including boundary.
|
||||||
|
Anchor is the minimum-x/minimum-z corner; the square may extend outside
|
||||||
|
this diamond. Coordinates are not clipped to Minecraft's world border.
|
||||||
|
Small squares can have enormous numbers of matching seeds and take very long.
|
||||||
|
)";}
|
||||||
|
int main(int argc,char**argv){try{
|
||||||
|
Config c;bool radiusSet=false,sizeSet=false;std::string patternText;bool patternSet=false;for(int i=1;i<argc;++i){std::string a=argv[i];if(a=="--help"||a=="-h"){help();return 0;}if(a=="--dry-run"){c.dry=true;continue;}if(a=="--list-devices"){c.list=true;continue;}
|
||||||
|
if(i+1>=argc)throw std::runtime_error("missing value for "+a);std::string v=argv[++i];
|
||||||
|
auto bounded=[&](uint64_t lo,uint64_t hi){auto n=number(v);if(n<lo||n>hi)throw std::runtime_error("out of range: "+a);return n;};
|
||||||
|
if(a=="--x")c.x=signed32(v);else if(a=="--z")c.z=signed32(v);else if(a=="--radius"){c.radius=bounded(0,INT32_MAX);radiusSet=true;}
|
||||||
|
else if(a=="--size"){c.size=bounded(1,6);sizeSet=true;}
|
||||||
|
else if(a=="--pattern"||a=="--pattern-file"){if(patternSet)throw std::runtime_error("specify only one pattern");patternSet=true;
|
||||||
|
if(a=="--pattern")patternText=v;else{std::ifstream file(v);if(!file)throw std::runtime_error("cannot read pattern: "+v);std::ostringstream text;text<<file.rdbuf();patternText=text.str();}}else if(a=="--devices")c.devices=v;else if(a=="--repeat")c.repeats=bounded(1,1000);
|
||||||
|
else if(a=="--position-batch")c.positionBatch=bounded(1,256);else if(a=="--job-batch")c.jobBatch=bounded(1,65536);
|
||||||
|
else if(a=="--cache-classes")c.cacheClasses=bounded(0,1000000);else if(a=="--max-results")c.maxResults=bounded(0,1000000);
|
||||||
|
else if(a=="--output")c.output=v;else throw std::runtime_error("unknown option: "+a);
|
||||||
|
}
|
||||||
|
if(c.list){int n;CUDA(cudaGetDeviceCount(&n));for(int i=0;i<n;++i){cudaDeviceProp p{};CUDA(cudaGetDeviceProperties(&p,i));std::cout<<i<<"\t"<<p.name<<"\tsm_"<<p.major<<p.minor<<"\t"<<p.totalGlobalMem/(1ULL<<20)<<" MiB\n";}return 0;}
|
||||||
|
if(!radiusSet)throw std::runtime_error("--radius is required");
|
||||||
|
if(patternSet&&sizeSet)throw std::runtime_error("--size and pattern options are mutually exclusive");
|
||||||
|
if(!patternSet){for(int z=0;z<c.size;++z){if(z)patternText+='/';patternText+=std::string(c.size,'#');}}
|
||||||
|
c.pattern=parsePattern(patternText);
|
||||||
|
for(auto axis:{std::make_pair(c.x,c.pattern.width),std::make_pair(c.z,c.pattern.height)})
|
||||||
|
if(int64_t(axis.first)-int64_t(c.radius)<INT32_MIN||int64_t(axis.first)+int64_t(c.radius)+axis.second-1>INT32_MAX)throw std::runtime_error("coordinates including pattern must fit int32");
|
||||||
|
if(!c.output.empty()&&std::ifstream(c.output).good())throw std::runtime_error("output already exists: "+c.output);
|
||||||
|
std::ostringstream report;report<<std::setprecision(12)<<"{\"schema_version\":1,\"mode\":\"ordinary_all_effective_seeds\",\"rejection_tail_included\":false,\"center\":["<<c.x<<","<<c.z<<"],\"radius_chunks\":"<<c.radius<<",\"square_size\":"<<(patternSet?"null":std::to_string(c.size))<<",\"anchors\":"<<anchors(c.radius)<<",\"effective_seed_bits\":48,\"dry_run\":"<<(c.dry?"true":"false");
|
||||||
|
report<<",\"pattern_width\":"<<c.pattern.width<<",\"pattern_height\":"<<c.pattern.height<<",\"slime_cells\":"<<c.pattern.positive<<",\"non_slime_cells\":"<<c.pattern.cells.size()-c.pattern.positive<<",\"pattern\":[";
|
||||||
|
for(size_t i=0;i<c.pattern.rows.size();++i){if(i)report<<",";report<<"\""<<c.pattern.rows[i]<<"\"";}report<<"]";
|
||||||
|
if(c.dry){auto first=coordinate(c,0),last=coordinate(c,anchors(c.radius)-1);report<<",\"first_anchor\":["<<first.x<<","<<first.z<<"],\"last_anchor\":["<<last.x<<","<<last.z<<"]}";}
|
||||||
|
else{
|
||||||
|
int available;CUDA(cudaGetDeviceCount(&available));std::vector<int> devices;if(c.devices=="all"){for(int i=0;i<available;++i)devices.push_back(i);}else{
|
||||||
|
std::stringstream ss(c.devices);std::string v;while(std::getline(ss,v,',')){auto id=number(v);if(id>=uint64_t(available))throw std::runtime_error("CUDA device unavailable: "+v);devices.push_back(id);}
|
||||||
|
}
|
||||||
|
if(devices.empty()||std::set<int>(devices.begin(),devices.end()).size()!=devices.size())throw std::runtime_error("device list must be nonempty and unique");
|
||||||
|
report<<",\"position_batch\":"<<c.positionBatch<<",\"job_batch\":"<<c.jobBatch<<",\"cache_classes\":"<<c.cacheClasses<<",\"cuda_runtime_version\":"<<CUDART_VERSION<<",\"rounds\":[";
|
||||||
|
std::vector<double> timings;uint64_t expected=0;
|
||||||
|
for(int round=0;round<c.repeats;++round){Shared shared;shared.workerCount=devices.size();std::vector<Metrics> metrics(devices.size());std::vector<std::thread> workers;auto start=Clock::now();
|
||||||
|
for(size_t i=0;i<devices.size();++i)workers.emplace_back([&,i](){worker(c,devices[i],shared,metrics[i]);++shared.finished;shared.cv.notify_all();});
|
||||||
|
{std::unique_lock<std::mutex> lock(shared.mutex);while(shared.finished<int(devices.size())){
|
||||||
|
if(!shared.cv.wait_for(lock,std::chrono::seconds(5),[&](){return shared.finished==int(devices.size());}))
|
||||||
|
std::cerr<<"progress: "<<shared.done<<"/"<<anchors(c.radius)<<" anchors (round "<<round+1<<")\n";
|
||||||
|
}}
|
||||||
|
for(auto&t:workers)t.join();
|
||||||
|
if(!shared.error.empty())throw std::runtime_error(shared.error);
|
||||||
|
double elapsed=std::chrono::duration<double,std::milli>(Clock::now()-start).count();auto first=metrics[0].start,last=metrics[0].end;
|
||||||
|
uint64_t visited=0,jobs=0,matches=0;std::vector<Match> sample;
|
||||||
|
for(auto&m:metrics){first=std::min(first,m.start);last=std::max(last,m.end);visited+=m.positions;jobs+=m.jobs;if(m.matches>UINT64_MAX-matches)throw std::runtime_error("total match count exceeds uint64");matches+=m.matches;for(auto hit:m.sample)if(sample.size()<c.maxResults)sample.push_back(hit);}
|
||||||
|
if(visited!=anchors(c.radius))throw std::runtime_error("incomplete anchor coverage");if(round&&matches!=expected)throw std::runtime_error("repeat match count mismatch");expected=matches;
|
||||||
|
double scan=std::chrono::duration<double,std::milli>(last-first).count();timings.push_back(scan);
|
||||||
|
if(round)report<<",";report<<"{\"round\":"<<round+1<<",\"elapsed_ms\":"<<elapsed<<",\"scan_wall_ms\":"<<scan<<",\"anchors_per_second\":"<<visited/(scan/1000)<<",\"root_jobs\":"<<jobs<<",\"matches\":"<<matches<<",\"sample_truncated\":"<<(matches>sample.size()?"true":"false")<<",\"sample\":[";
|
||||||
|
for(size_t i=0;i<sample.size();++i){if(i)report<<",";auto m=sample[i];report<<"["<<m.seed<<","<<m.x<<","<<m.z<<"]";}report<<"],\"gpus\":[";
|
||||||
|
for(size_t i=0;i<metrics.size();++i){if(i)report<<",";auto&m=metrics[i];report<<"{\"device\":"<<m.device<<",\"name\":\""<<escape(m.name)<<"\",\"anchors\":"<<m.positions<<",\"root_classes_generated\":"<<m.classes<<",\"cache_hits\":"<<m.cacheHits<<",\"root_jobs\":"<<m.jobs<<",\"matches\":"<<m.matches<<",\"setup_ms\":"<<m.setupMs<<",\"scan_ms\":"<<m.scanMs<<",\"root_stage_ms\":"<<m.rootMs<<",\"search_stage_ms\":"<<m.searchMs<<"}";}report<<"]}";
|
||||||
|
std::cerr<<"round "<<round+1<<"/"<<c.repeats<<": "<<visited<<" anchors, "<<matches<<" matches, scan "<<scan<<" ms, elapsed "<<elapsed<<" ms\n";
|
||||||
|
}
|
||||||
|
std::sort(timings.begin(),timings.end());double median=timings[timings.size()/2];if(timings.size()%2==0)median=(median+timings[timings.size()/2-1])/2;
|
||||||
|
report<<"],\"median_scan_wall_ms\":"<<median<<"}";
|
||||||
|
}
|
||||||
|
if(!c.output.empty()){std::ofstream file(c.output);if(!file)throw std::runtime_error("cannot write report: "+c.output);file<<report.str()<<"\n";file.close();if(!file)throw std::runtime_error("failed writing report");}
|
||||||
|
std::cout<<report.str()<<"\n";return 0;
|
||||||
|
}catch(const std::exception&e){std::cerr<<"error: "<<e.what()<<"\n";return 1;}}
|
||||||
133
bench/test_cli.py
Normal file
133
bench/test_cli.py
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse,json,subprocess,tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
HERE=Path(__file__).resolve().parent
|
||||||
|
BIN=HERE/'bin/slime-bench'
|
||||||
|
def run(*args,ok=True):
|
||||||
|
p=subprocess.run([str(BIN),*map(str,args)],capture_output=True,text=True,timeout=180)
|
||||||
|
if ok:
|
||||||
|
assert p.returncode==0,p.stderr
|
||||||
|
return json.loads(p.stdout)
|
||||||
|
assert p.returncode!=0,p.stdout
|
||||||
|
|
||||||
|
def term(x,z):
|
||||||
|
def i32(v):return ((v+2**31)%2**32)-2**31
|
||||||
|
return i32(x*x*4987142)+i32(x*5947611)+i32(z*z)*4392871+i32(z*389711)
|
||||||
|
def slime(seed,x,z):
|
||||||
|
s=((seed+term(x,z))^987234911^0x5DEECE66D)&((1<<48)-1)
|
||||||
|
while True:
|
||||||
|
s=(s*0x5DEECE66D+11)&((1<<48)-1);q=s>>17
|
||||||
|
if q<2147483640:return q%10==0
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap=argparse.ArgumentParser();ap.add_argument('--gpu',action='store_true');args=ap.parse_args()
|
||||||
|
for radius in [0,1,2,31,1000000]:
|
||||||
|
r=run('--x',-123,'--z',456,'--radius',radius,'--size',6,'--dry-run')
|
||||||
|
assert r['anchors']==1+2*radius*(radius+1)
|
||||||
|
assert r['first_anchor']==[-123,456-radius] and r['last_anchor']==[-123,456+radius]
|
||||||
|
for bad in [[],['--radius','-1'],['--radius','1','--size','0'],['--radius','1','--size','7'],['--radius','abc'],['--radius','0','--x','2147483647'],['--radius','1','--repeat','0'],['--radius','1','--position-batch','0']]:run(*bad,'--dry-run',ok=False)
|
||||||
|
pattern='?##./#..?/..#?'
|
||||||
|
r=run('--radius',0,'--pattern',pattern,'--dry-run')
|
||||||
|
assert r['pattern']==pattern.split('/') and r['square_size'] is None
|
||||||
|
assert (r['pattern_width'],r['pattern_height'],r['slime_cells'],r['non_slime_cells'])==(4,3,4,5)
|
||||||
|
for bad_pattern in ['', '.', '??', '#/##', '#x', '#//.', '#/'+'#'*65]:
|
||||||
|
run('--radius',0,'--pattern',bad_pattern,'--dry-run',ok=False)
|
||||||
|
run('--radius',0,'--size',5,'--pattern','#','--dry-run',ok=False)
|
||||||
|
run('--radius',0,'--pattern','#','--pattern','#','--dry-run',ok=False)
|
||||||
|
run('--radius',0,'--pattern-file','/nonexistent-pattern','--dry-run',ok=False)
|
||||||
|
run('--radius',0,'--x',2147483647,'--pattern','?#','--dry-run',ok=False)
|
||||||
|
r=run('--radius',0,'--x',2147483647,'--pattern','#/#','--dry-run')
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
path=Path(tmp)/'pattern.txt';path.write_bytes(b'?##.\r\n#..?\r\n..#?\r\n')
|
||||||
|
assert run('--radius',0,'--pattern-file',path,'--dry-run')['pattern']==pattern.split('/')
|
||||||
|
heart_fixture=json.loads((HERE/'results/heart-5x5-example.json').read_text())
|
||||||
|
assert (HERE/'patterns/heart-5x5.txt').read_text().splitlines()==heart_fixture['pattern']
|
||||||
|
assert all(slime(heart_fixture['seed48'],heart_fixture['chunk_x']+dx,heart_fixture['chunk_z']+dz)==(ch=='#')
|
||||||
|
for dz,row in enumerate(heart_fixture['pattern']) for dx,ch in enumerate(row))
|
||||||
|
summary={'cli_scope_and_errors':True,'pattern_inputs':True}
|
||||||
|
if args.gpu:
|
||||||
|
heart_poc=run('--x',heart_fixture['chunk_x'],'--z',heart_fixture['chunk_z'],'--radius',0,
|
||||||
|
'--pattern-file',HERE/'patterns/heart-5x5.txt','--max-results',2000)
|
||||||
|
heart_hits=heart_poc['rounds'][0]
|
||||||
|
assert heart_hits['matches']==heart_fixture['single_anchor']['matches']
|
||||||
|
assert heart_hits['root_jobs']==heart_fixture['single_anchor']['root_jobs']
|
||||||
|
assert not heart_hits['sample_truncated'] and len(heart_hits['sample'])==heart_hits['matches']
|
||||||
|
assert len(set(map(tuple,heart_hits['sample'])))==heart_hits['matches']
|
||||||
|
assert [heart_fixture['seed48'],heart_fixture['chunk_x'],heart_fixture['chunk_z']] in heart_hits['sample']
|
||||||
|
for seed,x,z in heart_hits['sample']:
|
||||||
|
assert all(slime(seed,x+dx,z+dz)==(ch=='#') for dz,row in enumerate(heart_fixture['pattern']) for dx,ch in enumerate(row))
|
||||||
|
summary['heart_pattern_verified_matches']=heart_hits['matches']
|
||||||
|
# A mixed-mask regression fixture; this is not the target creeper pattern.
|
||||||
|
fixture=json.loads((HERE/'testdata/mixed-mask-regression.json').read_text())
|
||||||
|
face=run('--x',fixture['chunk_x'],'--z',fixture['chunk_z'],'--radius',0,
|
||||||
|
'--pattern','/'.join(fixture['pattern']),'--max-results',1000)
|
||||||
|
hits=face['rounds'][0]
|
||||||
|
assert not hits['sample_truncated'] and any(s==fixture['seed48'] for s,x,z in hits['sample'])
|
||||||
|
for seed,x,z in hits['sample']:
|
||||||
|
assert all(slime(seed,x+dx,z+dz)==(ch=='#') for dz,row in enumerate(fixture['pattern']) for dx,ch in enumerate(row))
|
||||||
|
summary['mixed_mask_verified_matches']=hits['matches']
|
||||||
|
common=['--x',51069,'--z',419124]
|
||||||
|
reference=run(*common,'--radius',0,'--size',5,'--repeat',2,'--max-results',100)
|
||||||
|
expected={61692151016558,15959335054446}
|
||||||
|
assert all(r['matches']==24 and r['root_jobs']==84 for r in reference['rounds'])
|
||||||
|
samples=[set(map(tuple,r['sample'])) for r in reference['rounds']]
|
||||||
|
assert samples[0]==samples[1] and expected <= {s for s,x,z in samples[0]}
|
||||||
|
sliced=run(*common,'--radius',0,'--size',5,'--job-batch',17,'--max-results',100,'--cache-classes',0)
|
||||||
|
assert set(map(tuple,sliced['rounds'][0]['sample']))==samples[0]
|
||||||
|
partial=run(*common,'--radius',0,'--size',5,'--max-results',3)
|
||||||
|
assert partial['rounds'][0]['matches']==24 and len(partial['rounds'][0]['sample'])==3 and partial['rounds'][0]['sample_truncated']
|
||||||
|
for n in [4,6]:
|
||||||
|
r=run(*common,'--radius',0,'--size',n,'--max-results',1000)
|
||||||
|
for seed,x,z in r['rounds'][0]['sample']:assert all(slime(seed,x+dx,z+dz) for dx in range(n) for dz in range(n))
|
||||||
|
if n>=5:
|
||||||
|
wanted={t for t in samples[0] if all(slime(t[0],t[1]+dx,t[2]+dz) for dx in range(n) for dz in range(n))}
|
||||||
|
assert set(map(tuple,r['rounds'][0]['sample']))==wanted
|
||||||
|
summary[f'size_{n}']=r['rounds'][0]['matches']
|
||||||
|
# The same 5x5 positive constraints, shifted inside a non-square mask.
|
||||||
|
# Empty anchor exercises normalization independently of the first #.
|
||||||
|
rows=['?'*7]+['?'+'#'*5+'.' for _ in range(5)]+['?.?????']
|
||||||
|
pattern='/'.join(rows)
|
||||||
|
masked=run('--x',51068,'--z',419123,'--radius',0,'--pattern',pattern,'--max-results',100)
|
||||||
|
wanted={(seed,51068,419123) for seed,x,z in samples[0]
|
||||||
|
if all(ch=='?' or slime(seed,51068+dx,419123+dz)==(ch=='#')
|
||||||
|
for dz,row in enumerate(rows) for dx,ch in enumerate(row))}
|
||||||
|
assert wanted and set(map(tuple,masked['rounds'][0]['sample']))==wanted
|
||||||
|
assert masked['rounds'][0]['matches']==len(wanted)
|
||||||
|
# Identical positive subset, but ignored rather than forbidden background.
|
||||||
|
ignored=pattern.replace('.', '?')
|
||||||
|
loose=run('--x',51068,'--z',419123,'--radius',0,'--pattern',ignored,'--max-results',100)
|
||||||
|
assert {s for s,x,z in loose['rounds'][0]['sample']}=={s for s,x,z in samples[0]}
|
||||||
|
# Force a forbidden cell to reject at least one known positive candidate.
|
||||||
|
seed=next(iter(samples[0]))[0]
|
||||||
|
dx,dz=next((dx,dz) for dz in range(16) for dx in range(16)
|
||||||
|
if not (1<=dx<=5 and 1<=dz<=5) and slime(seed,51068+dx,419123+dz))
|
||||||
|
rejection=[list('?'*16) for _ in range(16)]
|
||||||
|
for z in range(1,6):
|
||||||
|
for x in range(1,6):rejection[z][x]='#'
|
||||||
|
rejection[dz][dx]='.'
|
||||||
|
rejected=run('--x',51068,'--z',419123,'--radius',0,'--pattern',
|
||||||
|
'/'.join(''.join(row) for row in rejection),'--max-results',100)
|
||||||
|
expected_seeds={s for s,x,z in samples[0] if not slime(s,51068+dx,419123+dz)}
|
||||||
|
assert len(expected_seeds)<24
|
||||||
|
assert {s for s,x,z in rejected['rounds'][0]['sample']}==expected_seeds
|
||||||
|
assert rejected['rounds'][0]['matches']==len(expected_seeds)
|
||||||
|
summary['mixed_pattern_vs_independent_filter']=len(wanted)
|
||||||
|
summary['forbidden_cell_excludes_candidates']=True
|
||||||
|
diamond=run(*common,'--radius',2,'--size',5,'--max-results',1000,'--position-batch',3)
|
||||||
|
split=[]
|
||||||
|
for dz in range(-2,3):
|
||||||
|
for dx in range(-2,3):
|
||||||
|
if abs(dx)+abs(dz)>2:continue
|
||||||
|
r=run('--x',51069+dx,'--z',419124+dz,'--radius',0,'--size',5,'--max-results',1000)
|
||||||
|
split.extend(map(tuple,r['rounds'][0]['sample']))
|
||||||
|
assert sorted(split)==sorted(map(tuple,diamond['rounds'][0]['sample']))
|
||||||
|
assert diamond['anchors']==13
|
||||||
|
run('--radius',0,'--devices','0,0',ok=False)
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
out=Path(tmp)/'report.json'
|
||||||
|
r=run(*common,'--radius',0,'--output',out)
|
||||||
|
assert json.loads(out.read_text())==r
|
||||||
|
run(*common,'--radius',0,'--output',out,ok=False)
|
||||||
|
summary['gpu_known_matches']=24;summary['diamond_vs_individual_anchors']=True
|
||||||
|
print(json.dumps(summary,indent=2))
|
||||||
|
if __name__=='__main__':main()
|
||||||
64
bench/test_math.cu
Normal file
64
bench/test_math.cu
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
#define main cli_entry_for_tests
|
||||||
|
#include "slime_bench.cu"
|
||||||
|
#undef main
|
||||||
|
#include <random>
|
||||||
|
struct HostEmit{
|
||||||
|
const sr::CellTerm* cells;int count;std::set<uint64_t>* result;
|
||||||
|
__host__ __device__ void operator()(uint64_t seed){
|
||||||
|
#ifndef __CUDA_ARCH__
|
||||||
|
if(sr::verifyPattern(seed,cells,count))result->insert(seed);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void check(bool ok,const char*msg){if(!ok)throw std::runtime_error(msg);}
|
||||||
|
int main(){
|
||||||
|
uint64_t points=0,groups=0,mixed=0;std::mt19937_64 rng(123456);
|
||||||
|
for(int r=0;r<=64;++r){Config c;c.x=-123;c.z=98;c.radius=r;std::set<std::pair<int32_t,int32_t>> actual;
|
||||||
|
for(uint64_t i=0;i<anchors(r);++i){auto p=coordinate(c,i);actual.emplace(p.x,p.z);}
|
||||||
|
check(actual.size()==anchors(r),"duplicate anchor");
|
||||||
|
for(int dz=-r;dz<=r;++dz)for(int dx=-r;dx<=r;++dx)check(bool(actual.count({c.x+dx,c.z+dz}))==(std::abs(dx)+std::abs(dz)<=r),"diamond coverage mismatch");points+=actual.size();
|
||||||
|
}
|
||||||
|
Config large;large.radius=1000000;
|
||||||
|
for(int t=0;t<100000;++t){auto p=coordinate(large,rng()%anchors(large.radius));check(std::abs(int64_t(p.x))+std::abs(int64_t(p.z))<=int64_t(large.radius),"large diamond mismatch");}
|
||||||
|
for(int n:{1,2,3,4,5,6})for(int t=0;t<512;++t){
|
||||||
|
Coord coord{int32_t(rng()%3750000)-1875000,int32_t(rng()%3750000)-1875000};int count=n*n;
|
||||||
|
std::vector<sr::CellTerm> cells(count);terms(coord,n,cells.data());uint32_t root=rng()&262143,base=(rng()&131071)&~240U;
|
||||||
|
std::vector<uint32_t> prepared(count*2);
|
||||||
|
for(int i=0;i<count;++i){uint64_t a=root+cells[i].term;prepared[2*i]=a>>18;prepared[2*i+1]=((((a^sr::kCombinedXor)&262143)*sr::kJavaMultiplier+11)>>17);
|
||||||
|
for(int j=0;j<16;++j)check(q(prepared.data(),i,base+16*j)==(sr::nextStateFromTerm(root+(uint64_t(base+16*j)<<18),cells[i].term)>>17),"prepared q mismatch");}
|
||||||
|
uint32_t expected=0;for(int j=0;j<16;++j)if(fs::differenceFilter(root+(uint64_t(base+16*j)<<18),cells.data(),count))expected|=1U<<j;
|
||||||
|
check(groupMask(prepared.data(),base,count)==expected,"variable-size group mask mismatch");++groups;
|
||||||
|
}
|
||||||
|
// Compare arbitrary sparse layouts and forbidden cells against all high bits.
|
||||||
|
for(int t=0;t<128;++t){
|
||||||
|
Coord coord{int32_t(rng()),int32_t(rng())};
|
||||||
|
// Keep offsets away from overflow even for signed boundary samples.
|
||||||
|
coord.x/=2;coord.z/=2;
|
||||||
|
uint64_t world=rng()&sr::kMask48;
|
||||||
|
std::string text;
|
||||||
|
for(int z=0;z<5;++z){if(z)text+='/';for(int x=0;x<7;++x)
|
||||||
|
text+=(x==0&&z==0)||rng()%4==0?'?':sr::exactIsSlimeFromTerm(world,sr::coordinateTerm(coord.x+x,coord.z+z))?'#':'.';}
|
||||||
|
if(text.find('#')==std::string::npos)continue;
|
||||||
|
auto pattern=parsePattern(text);int count=pattern.cells.size();
|
||||||
|
std::vector<sr::CellTerm> cells(count);terms(coord,pattern,cells.data());
|
||||||
|
uint64_t normalized=(world+sr::coordinateTerm(coord.x+pattern.cells.front().x,coord.z+pattern.cells.front().z))&sr::kMask48;
|
||||||
|
check(sr::verifyPattern(normalized,cells.data(),count),"normalized pattern mismatch");
|
||||||
|
uint64_t low=normalized&((1ULL<<35)-1);std::set<uint64_t> actual,expected;
|
||||||
|
HostEmit emit{cells.data()+pattern.positive,count-pattern.positive,&actual};
|
||||||
|
fs::solveIntervals(low,cells.data(),pattern.positive,emit);
|
||||||
|
for(uint64_t high=0;high<8192;++high){uint64_t seed=low+(high<<35);
|
||||||
|
// Ordinary branch requires a successful first draw at positive cells.
|
||||||
|
bool ordinary=true;for(int i=0;i<pattern.positive;++i)
|
||||||
|
if((sr::nextStateFromTerm(seed,cells[i].term)>>17)%10!=0)ordinary=false;
|
||||||
|
if(ordinary&&sr::verifyPattern(seed,cells.data(),count))expected.insert(seed);
|
||||||
|
}
|
||||||
|
check(actual==expected,"mixed pattern high-bit solver mismatch");++mixed;
|
||||||
|
uint32_t root=low&262143,base=uint32_t(low>>18)&~240U;
|
||||||
|
std::vector<uint32_t> prepared(count*2);
|
||||||
|
for(int i=0;i<pattern.positive;++i){uint64_t a=root+cells[i].term;
|
||||||
|
prepared[2*i]=a>>18;prepared[2*i+1]=((((a^sr::kCombinedXor)&262143)*sr::kJavaMultiplier+11)>>17);}
|
||||||
|
uint32_t wanted=0;for(int j=0;j<16;++j)if(fs::differenceFilter(root+(uint64_t(base+16*j)<<18),cells.data(),pattern.positive))wanted|=1U<<j;
|
||||||
|
check(groupMask(prepared.data(),base,pattern.positive)==wanted,"sparse pattern group mask mismatch");
|
||||||
|
}
|
||||||
|
std::cout<<"{\"diamond_points\":"<<points<<",\"variable_size_groups\":"<<groups<<",\"mixed_pattern_slices\":"<<mixed<<",\"passed\":true}\n";
|
||||||
|
}
|
||||||
26
bench/testdata/mixed-mask-regression.json
vendored
Normal file
26
bench/testdata/mixed-mask-regression.json
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"pattern": [
|
||||||
|
"..##..",
|
||||||
|
"..##..",
|
||||||
|
"##..##",
|
||||||
|
"#....#",
|
||||||
|
"#....#",
|
||||||
|
"#.##.#"
|
||||||
|
],
|
||||||
|
"seed48": 100568620033461,
|
||||||
|
"chunk_x": -137,
|
||||||
|
"chunk_z": -53,
|
||||||
|
"independent_verification": true,
|
||||||
|
"search": {
|
||||||
|
"center": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"radius_chunks": 256,
|
||||||
|
"anchors": 131585,
|
||||||
|
"matches": 1248,
|
||||||
|
"scan_wall_ms": 2968.438239,
|
||||||
|
"gpu": "NVIDIA GeForce RTX 3060 Laptop GPU",
|
||||||
|
"rejection_tail_included": false
|
||||||
|
}
|
||||||
|
}
|
||||||
44
exp/README.md
Normal file
44
exp/README.md
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Slime Reverse Search Experiment
|
||||||
|
|
||||||
|
This folder is for experiments that search "backwards" from a desired slime
|
||||||
|
chunk pattern instead of scanning every `(seed, x, z)` tuple directly.
|
||||||
|
|
||||||
|
## SMT Bit-Vector Prototype
|
||||||
|
|
||||||
|
`slime_reverse_smt.py` emits an SMT-LIB query with these variables:
|
||||||
|
|
||||||
|
- `seed48`: the low 48 bits of the world seed
|
||||||
|
- `chunkX`, `chunkZ`: the top-left chunk coordinate, unless fixed with `--x/--z`
|
||||||
|
|
||||||
|
It uses the approximate predicate:
|
||||||
|
|
||||||
|
```text
|
||||||
|
((LCG48(seed48 + coordTerm(x, z)) >> 17) % 10) == 0
|
||||||
|
```
|
||||||
|
|
||||||
|
That removes Java `nextInt(10)`'s extremely rare rejection-loop branch. Models
|
||||||
|
from this query are candidates and should be verified with the exact predicate.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate a 4x4 query with variable coordinates in [-64, 64].
|
||||||
|
python3 exp/slime_reverse_smt.py -n 4 -r 64 -o /tmp/slime4.smt2
|
||||||
|
z3 /tmp/slime4.smt2
|
||||||
|
|
||||||
|
# Fix the top-left coordinate and solve only for seed48.
|
||||||
|
python3 exp/slime_reverse_smt.py -n 4 --x 0 --z 0 --run --verify
|
||||||
|
|
||||||
|
# Start with a cheaper partial query before asking for all cells.
|
||||||
|
python3 exp/slime_reverse_smt.py -n 5 -r 128 --cells border --run --verify
|
||||||
|
python3 exp/slime_reverse_smt.py -n 5 -r 128 --cells corners --run
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Variable `chunkX/chunkZ` introduce bit-vector multiplication, so large-radius
|
||||||
|
full 5x5 queries may be very hard for a solver.
|
||||||
|
- Fixed-coordinate queries are much cleaner and are useful for testing whether a
|
||||||
|
solver can handle many seed constraints.
|
||||||
|
- Exact verification is included because the SMT predicate intentionally ignores
|
||||||
|
Java's rejection retry.
|
||||||
228
exp/slime_reverse_smt.py
Normal file
228
exp/slime_reverse_smt.py
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate an SMT-LIB bit-vector query for reverse-searching slime chunk patterns.
|
||||||
|
|
||||||
|
This is an experiment, not a replacement for the CUDA brute-force scanner. It
|
||||||
|
uses an approximate nextInt(10) predicate with the Java rejection loop removed:
|
||||||
|
|
||||||
|
((next48 >> 17) % 10) == 0
|
||||||
|
|
||||||
|
The approximation is useful because it removes the rare retry branch and gives a
|
||||||
|
clean fixed one-step LCG constraint. Any model from this script should still be
|
||||||
|
verified with the exact Java nextInt(10) implementation before being trusted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
JAVA_MULT = 0x5DEECE66D
|
||||||
|
JAVA_ADD = 0xB
|
||||||
|
SLIME_XOR = 987234911
|
||||||
|
|
||||||
|
|
||||||
|
def bv(value: int, bits: int) -> str:
|
||||||
|
return f"(_ bv{value & ((1 << bits) - 1)} {bits})"
|
||||||
|
|
||||||
|
|
||||||
|
def signed_bv32(value: int) -> str:
|
||||||
|
return bv(value, 32)
|
||||||
|
|
||||||
|
|
||||||
|
def cell_order(n: int, mode: str) -> list[tuple[int, int]]:
|
||||||
|
cells = [(dx, dz) for dx in range(n) for dz in range(n)]
|
||||||
|
if mode == "all":
|
||||||
|
return cells
|
||||||
|
if mode == "corners":
|
||||||
|
return [(0, 0), (n - 1, 0), (0, n - 1), (n - 1, n - 1)]
|
||||||
|
if mode == "border":
|
||||||
|
return [(dx, dz) for dx, dz in cells if dx in (0, n - 1) or dz in (0, n - 1)]
|
||||||
|
if mode == "diagonal":
|
||||||
|
return [(i, i) for i in range(n)]
|
||||||
|
raise ValueError(f"unknown cell mode: {mode}")
|
||||||
|
|
||||||
|
|
||||||
|
def smt_prelude() -> str:
|
||||||
|
xor48 = bv(SLIME_XOR, 48)
|
||||||
|
mult48 = bv(JAVA_MULT, 48)
|
||||||
|
add48 = bv(JAVA_ADD, 48)
|
||||||
|
return f"""
|
||||||
|
(set-logic QF_BV)
|
||||||
|
|
||||||
|
(define-fun i32_to_i64 ((v (_ BitVec 32))) (_ BitVec 64)
|
||||||
|
((_ sign_extend 32) v))
|
||||||
|
|
||||||
|
(define-fun coordXTerm64 ((x (_ BitVec 32))) (_ BitVec 64)
|
||||||
|
(bvadd
|
||||||
|
(i32_to_i64 (bvmul (bvmul x x) {bv(4987142, 32)}))
|
||||||
|
(i32_to_i64 (bvmul x {bv(5947611, 32)}))))
|
||||||
|
|
||||||
|
(define-fun coordZTerm64 ((z (_ BitVec 32))) (_ BitVec 64)
|
||||||
|
(bvadd
|
||||||
|
(bvmul (i32_to_i64 (bvmul z z)) {bv(4392871, 64)})
|
||||||
|
(i32_to_i64 (bvmul z {bv(389711, 32)}))))
|
||||||
|
|
||||||
|
(define-fun coordTerm48 ((x (_ BitVec 32)) (z (_ BitVec 32))) (_ BitVec 48)
|
||||||
|
((_ extract 47 0) (bvadd (coordXTerm64 x) (coordZTerm64 z))))
|
||||||
|
|
||||||
|
(define-fun lcgNext48 ((state (_ BitVec 48))) (_ BitVec 48)
|
||||||
|
(bvadd (bvmul state {mult48}) {add48}))
|
||||||
|
|
||||||
|
(define-fun slimeApprox ((seed48 (_ BitVec 48)) (x (_ BitVec 32)) (z (_ BitVec 32))) Bool
|
||||||
|
(= (bvurem
|
||||||
|
((_ extract 47 17)
|
||||||
|
(lcgNext48
|
||||||
|
(bvxor (bvxor (bvadd seed48 (coordTerm48 x z)) {xor48}) {mult48})))
|
||||||
|
{bv(10, 31)})
|
||||||
|
{bv(0, 31)}))
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def generate(args: argparse.Namespace) -> str:
|
||||||
|
lines: list[str] = [smt_prelude()]
|
||||||
|
lines.append("(declare-const seed48 (_ BitVec 48))")
|
||||||
|
|
||||||
|
if args.seed_start is not None:
|
||||||
|
lines.append(f"(assert (bvuge seed48 {bv(args.seed_start, 48)}))")
|
||||||
|
if args.seed_end is not None:
|
||||||
|
lines.append(f"(assert (bvult seed48 {bv(args.seed_end, 48)}))")
|
||||||
|
|
||||||
|
if args.x is None:
|
||||||
|
lines.append("(declare-const chunkX (_ BitVec 32))")
|
||||||
|
lines.append(f"(assert (bvsge chunkX {signed_bv32(-args.radius)}))")
|
||||||
|
lines.append(f"(assert (bvsle chunkX {signed_bv32(args.radius)}))")
|
||||||
|
else:
|
||||||
|
lines.append(f"(define-fun chunkX () (_ BitVec 32) {signed_bv32(args.x)})")
|
||||||
|
|
||||||
|
if args.z is None:
|
||||||
|
lines.append("(declare-const chunkZ (_ BitVec 32))")
|
||||||
|
lines.append(f"(assert (bvsge chunkZ {signed_bv32(-args.radius)}))")
|
||||||
|
lines.append(f"(assert (bvsle chunkZ {signed_bv32(args.radius)}))")
|
||||||
|
else:
|
||||||
|
lines.append(f"(define-fun chunkZ () (_ BitVec 32) {signed_bv32(args.z)})")
|
||||||
|
|
||||||
|
cells = cell_order(args.n, args.cells)
|
||||||
|
if args.limit_cells is not None:
|
||||||
|
cells = cells[: args.limit_cells]
|
||||||
|
|
||||||
|
for dx, dz in cells:
|
||||||
|
lines.append(
|
||||||
|
"(assert (slimeApprox seed48 "
|
||||||
|
f"(bvadd chunkX {bv(dx, 32)}) "
|
||||||
|
f"(bvadd chunkZ {bv(dz, 32)})))"
|
||||||
|
)
|
||||||
|
|
||||||
|
lines.append("(check-sat)")
|
||||||
|
lines.append("(get-value (seed48 chunkX chunkZ))")
|
||||||
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_model(output: str) -> tuple[int | None, int | None, int | None]:
|
||||||
|
values: dict[str, int] = {}
|
||||||
|
for name, token in re.findall(r"\((seed48|chunkX|chunkZ)\s+(#x[0-9a-fA-F]+|\(_ bv\d+ \d+\))\)", output):
|
||||||
|
if token.startswith("#x"):
|
||||||
|
values[name] = int(token[2:], 16)
|
||||||
|
else:
|
||||||
|
match = re.match(r"\(_ bv(\d+) \d+\)", token)
|
||||||
|
if match:
|
||||||
|
values[name] = int(match.group(1))
|
||||||
|
|
||||||
|
def as_i32(value: int | None) -> int | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
value &= 0xFFFFFFFF
|
||||||
|
return value - 0x100000000 if value & 0x80000000 else value
|
||||||
|
|
||||||
|
return values.get("seed48"), as_i32(values.get("chunkX")), as_i32(values.get("chunkZ"))
|
||||||
|
|
||||||
|
|
||||||
|
def java_int_as_long(value: int) -> int:
|
||||||
|
value &= 0xFFFFFFFF
|
||||||
|
return value - 0x100000000 if value & 0x80000000 else value
|
||||||
|
|
||||||
|
|
||||||
|
def coord_term(x: int, z: int) -> int:
|
||||||
|
return (
|
||||||
|
java_int_as_long(x * x * 4987142)
|
||||||
|
+ java_int_as_long(x * 5947611)
|
||||||
|
+ java_int_as_long(z * z) * 4392871
|
||||||
|
+ java_int_as_long(z * 389711)
|
||||||
|
) & ((1 << 64) - 1)
|
||||||
|
|
||||||
|
|
||||||
|
def exact_slime(seed48: int, x: int, z: int) -> bool:
|
||||||
|
state = ((seed48 + coord_term(x, z)) ^ SLIME_XOR ^ JAVA_MULT) & ((1 << 48) - 1)
|
||||||
|
while True:
|
||||||
|
state = (state * JAVA_MULT + JAVA_ADD) & ((1 << 48) - 1)
|
||||||
|
bits = state >> 17
|
||||||
|
val = bits % 10
|
||||||
|
if bits - val + 9 <= 0x7FFFFFFF:
|
||||||
|
return val == 0
|
||||||
|
|
||||||
|
|
||||||
|
def verify(seed48: int, x: int, z: int, n: int) -> bool:
|
||||||
|
return all(exact_slime(seed48, x + dx, z + dz) for dx in range(n) for dz in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Generate/run an SMT reverse search for slime chunk squares.")
|
||||||
|
parser.add_argument("-n", type=int, default=4, help="square size to constrain")
|
||||||
|
parser.add_argument("-r", "--radius", type=int, default=128, help="signed coordinate bound when x/z are not fixed")
|
||||||
|
parser.add_argument("--x", type=int, help="fix left x coordinate")
|
||||||
|
parser.add_argument("--z", type=int, help="fix top z coordinate")
|
||||||
|
parser.add_argument("-s", "--seed-start", type=int, help="inclusive low-48 seed bound")
|
||||||
|
parser.add_argument("-e", "--seed-end", type=int, help="exclusive low-48 seed bound")
|
||||||
|
parser.add_argument("--cells", choices=["all", "border", "corners", "diagonal"], default="all")
|
||||||
|
parser.add_argument("--limit-cells", type=int, help="only assert the first k cells from the selected order")
|
||||||
|
parser.add_argument("-o", "--output", default="exp/slime_reverse.smt2", help="SMT-LIB output path")
|
||||||
|
parser.add_argument("--run", action="store_true", help="run the solver after writing the query")
|
||||||
|
parser.add_argument("--solver", default="z3", help="solver executable, e.g. z3, boolector, cvc5")
|
||||||
|
parser.add_argument("--verify", action="store_true", help="verify a SAT model with exact Java nextInt(10)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.n <= 0 or args.radius < 0:
|
||||||
|
parser.error("n must be positive and radius must be non-negative")
|
||||||
|
if args.seed_start is not None and args.seed_end is not None and args.seed_start >= args.seed_end:
|
||||||
|
parser.error("seed-start must be less than seed-end")
|
||||||
|
|
||||||
|
output_path = pathlib.Path(args.output)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_path.write_text(generate(args), encoding="utf-8")
|
||||||
|
print(f"wrote {output_path}")
|
||||||
|
|
||||||
|
if not args.run:
|
||||||
|
print(f"run with: {args.solver} {output_path}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
solver = shutil.which(args.solver)
|
||||||
|
if solver is None:
|
||||||
|
print(f"solver not found: {args.solver}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
completed = subprocess.run([solver, str(output_path)], text=True, capture_output=True, check=False)
|
||||||
|
if completed.stdout:
|
||||||
|
print(completed.stdout, end="")
|
||||||
|
if completed.stderr:
|
||||||
|
print(completed.stderr, file=sys.stderr, end="")
|
||||||
|
if completed.returncode != 0:
|
||||||
|
return completed.returncode
|
||||||
|
|
||||||
|
first_line = completed.stdout.splitlines()[0].strip() if completed.stdout.splitlines() else ""
|
||||||
|
if args.verify and first_line == "sat":
|
||||||
|
seed48, x, z = parse_model(completed.stdout)
|
||||||
|
if seed48 is None or x is None or z is None:
|
||||||
|
print("could not parse model for exact verification", file=sys.stderr)
|
||||||
|
return 3
|
||||||
|
ok = verify(seed48, x, z, args.n)
|
||||||
|
print(f"exact_verify seed48={seed48} x={x} z={z} n={args.n}: {'pass' if ok else 'fail'}")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
8
include/README.md
Normal file
8
include/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 共用算法头文件
|
||||||
|
|
||||||
|
- `slime/solver_core.hpp`:Java 整数回绕、48 位随机数、精确史莱姆判定、低位约束和原高位求解器。
|
||||||
|
- `slime/upper_interval.hpp`:输出差必要条件与高位区间求交,用于正约束(可稀疏图案)的普通候选分支;CLI 在发出结果前核对非史莱姆约束。
|
||||||
|
|
||||||
|
CLI 直接依赖这里。`research/slime_solver_core.hpp` 和原实验目录的 `interval_solver.hpp` 仅转发包含,以便已有研究程序、教学代码和历史复现命令继续工作。
|
||||||
|
|
||||||
|
保留现有命名空间及算法语义。精确验证支持 Java 重试,不代表普通候选生成已经包含拒绝尾部补扫。
|
||||||
286
include/slime/solver_core.hpp
Normal file
286
include/slime/solver_core.hpp
Normal file
|
|
@ -0,0 +1,286 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#if defined(__CUDACC__)
|
||||||
|
#define SLIME_HD __host__ __device__
|
||||||
|
#define SLIME_INLINE __forceinline__
|
||||||
|
#else
|
||||||
|
#define SLIME_HD
|
||||||
|
#define SLIME_INLINE inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace slime_research {
|
||||||
|
|
||||||
|
constexpr uint64_t kJavaMultiplier = 0x5DEECE66DULL;
|
||||||
|
constexpr uint64_t kJavaAddend = 0xBULL;
|
||||||
|
constexpr uint64_t kMask48 = (1ULL << 48) - 1ULL;
|
||||||
|
constexpr uint64_t kSlimeXor = 987234911ULL;
|
||||||
|
constexpr uint64_t kCombinedXor = kSlimeXor ^ kJavaMultiplier;
|
||||||
|
constexpr int kLowerBits = 35;
|
||||||
|
constexpr int kUpperBits = 13;
|
||||||
|
constexpr uint32_t kUpperSize = 1U << kUpperBits;
|
||||||
|
constexpr uint32_t kUpperMask = kUpperSize - 1U;
|
||||||
|
constexpr uint32_t kHalfDeltaInverse = 4965U;
|
||||||
|
constexpr int kMaxPatternCells = 64;
|
||||||
|
constexpr uint32_t kX2Coeff = 4987142U;
|
||||||
|
constexpr uint32_t kXCoeff = 5947611U;
|
||||||
|
constexpr uint32_t kZ2Coeff = 4392871U;
|
||||||
|
constexpr uint32_t kZCoeff = 389711U;
|
||||||
|
|
||||||
|
enum Requirement : uint8_t {
|
||||||
|
kNonSlime = 0,
|
||||||
|
kSlime = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CellTerm {
|
||||||
|
uint64_t term;
|
||||||
|
uint8_t requirement;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UpperEvent {
|
||||||
|
uint16_t threshold;
|
||||||
|
uint8_t before;
|
||||||
|
};
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE uint64_t javaIntAsLongBits(uint32_t value) {
|
||||||
|
return static_cast<uint64_t>(static_cast<int64_t>(static_cast<int32_t>(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE uint64_t coordinateTerm(int32_t chunkX, int32_t chunkZ) {
|
||||||
|
const uint32_t x = static_cast<uint32_t>(chunkX);
|
||||||
|
const uint32_t z = static_cast<uint32_t>(chunkZ);
|
||||||
|
return javaIntAsLongBits(x * x * kX2Coeff) +
|
||||||
|
javaIntAsLongBits(x * kXCoeff) +
|
||||||
|
javaIntAsLongBits(z * z) * kZ2Coeff +
|
||||||
|
javaIntAsLongBits(z * kZCoeff);
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE uint64_t coordinateXTerm(int32_t chunkX) {
|
||||||
|
const uint32_t x = static_cast<uint32_t>(chunkX);
|
||||||
|
return javaIntAsLongBits(x * x * kX2Coeff) +
|
||||||
|
javaIntAsLongBits(x * kXCoeff);
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE uint64_t coordinateZTerm(int32_t chunkZ) {
|
||||||
|
const uint32_t z = static_cast<uint32_t>(chunkZ);
|
||||||
|
return javaIntAsLongBits(z * z) * kZ2Coeff +
|
||||||
|
javaIntAsLongBits(z * kZCoeff);
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE uint64_t nextStateFromTerm(uint64_t seed48, uint64_t term) {
|
||||||
|
uint64_t state = ((seed48 + term) ^ kCombinedXor) & kMask48;
|
||||||
|
return (state * kJavaMultiplier + kJavaAddend) & kMask48;
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE bool exactIsSlimeFromTerm(uint64_t seed48, uint64_t term) {
|
||||||
|
uint64_t state = ((seed48 + term) ^ kCombinedXor) & kMask48;
|
||||||
|
for (;;) {
|
||||||
|
state = (state * kJavaMultiplier + kJavaAddend) & kMask48;
|
||||||
|
const uint32_t bits = static_cast<uint32_t>(state >> 17);
|
||||||
|
const uint32_t value = bits % 10U;
|
||||||
|
if (bits - value + 9U <= 0x7FFFFFFFU) {
|
||||||
|
return value == 0U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE bool verifyPattern(
|
||||||
|
uint64_t seed48,
|
||||||
|
const CellTerm* cells,
|
||||||
|
int cellCount
|
||||||
|
) {
|
||||||
|
for (int i = 0; i < cellCount; ++i) {
|
||||||
|
const bool slime = exactIsSlimeFromTerm(seed48, cells[i].term);
|
||||||
|
if (slime != (cells[i].requirement == kSlime)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE bool partialPatternPossible(
|
||||||
|
uint64_t seedPrefix,
|
||||||
|
int knownBits,
|
||||||
|
const CellTerm* cells,
|
||||||
|
int cellCount
|
||||||
|
) {
|
||||||
|
const uint64_t mask = (1ULL << knownBits) - 1ULL;
|
||||||
|
seedPrefix &= mask;
|
||||||
|
for (int i = 0; i < cellCount; ++i) {
|
||||||
|
uint64_t state = (((seedPrefix + cells[i].term) ^ kCombinedXor) *
|
||||||
|
kJavaMultiplier + kJavaAddend) & mask;
|
||||||
|
const uint64_t knownOutput = state >> 17;
|
||||||
|
if (cells[i].requirement == kSlime && (knownOutput & 1ULL) != 0ULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE uint32_t firstWithResidue(
|
||||||
|
uint32_t start,
|
||||||
|
uint32_t residue
|
||||||
|
) {
|
||||||
|
return start + ((residue + 5U - (start % 5U)) % 5U);
|
||||||
|
}
|
||||||
|
|
||||||
|
SLIME_HD SLIME_INLINE bool upper13HasCandidate(
|
||||||
|
uint64_t lower35,
|
||||||
|
const CellTerm* cells,
|
||||||
|
int cellCount
|
||||||
|
) {
|
||||||
|
UpperEvent events[kMaxPatternCells];
|
||||||
|
uint16_t residueCounts[5] = {0, 0, 0, 0, 0};
|
||||||
|
int eventCount = 0;
|
||||||
|
lower35 &= (1ULL << kLowerBits) - 1ULL;
|
||||||
|
for (int i = 0; i < cellCount; ++i) {
|
||||||
|
if (cells[i].requirement != kSlime) continue;
|
||||||
|
const uint32_t output = static_cast<uint32_t>(
|
||||||
|
nextStateFromTerm(lower35, cells[i].term) >> 17);
|
||||||
|
const uint32_t low17 = output & ((1U << 17) - 1U);
|
||||||
|
if ((low17 & 1U) != 0U) return false;
|
||||||
|
const uint32_t upper14 = output >> 17;
|
||||||
|
const uint32_t parity = upper14 & 1U;
|
||||||
|
const uint32_t v0 = (upper14 - parity) >> 1;
|
||||||
|
const uint32_t wanted = (3U * ((5U - ((low17 >> 1) % 5U) +
|
||||||
|
5U - parity) % 5U)) % 5U;
|
||||||
|
const uint32_t before = (wanted + 5U - (v0 % 5U)) % 5U;
|
||||||
|
const UpperEvent event{static_cast<uint16_t>(kUpperSize - v0),
|
||||||
|
static_cast<uint8_t>(before)};
|
||||||
|
int insert = eventCount;
|
||||||
|
while (insert > 0 && events[insert - 1].threshold > event.threshold) {
|
||||||
|
events[insert] = events[insert - 1];
|
||||||
|
--insert;
|
||||||
|
}
|
||||||
|
events[insert] = event;
|
||||||
|
++eventCount;
|
||||||
|
++residueCounts[before];
|
||||||
|
}
|
||||||
|
if (eventCount == 0) return true;
|
||||||
|
uint32_t start = 0;
|
||||||
|
int index = 0;
|
||||||
|
for (;;) {
|
||||||
|
const uint32_t stop = index < eventCount ? events[index].threshold : kUpperSize;
|
||||||
|
for (uint32_t residue = 0; residue < 5; ++residue) {
|
||||||
|
if (residueCounts[residue] == eventCount &&
|
||||||
|
firstWithResidue(start, residue) < stop) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index == eventCount) return false;
|
||||||
|
const uint32_t threshold = events[index].threshold;
|
||||||
|
while (index < eventCount && events[index].threshold == threshold) {
|
||||||
|
const uint32_t before = events[index].before;
|
||||||
|
--residueCounts[before];
|
||||||
|
++residueCounts[(before + 2U) % 5U];
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
start = threshold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Emit>
|
||||||
|
SLIME_HD SLIME_INLINE uint32_t solveUpper13(
|
||||||
|
uint64_t lower35,
|
||||||
|
const CellTerm* cells,
|
||||||
|
int cellCount,
|
||||||
|
Emit& emit
|
||||||
|
) {
|
||||||
|
UpperEvent events[kMaxPatternCells];
|
||||||
|
uint16_t residueCounts[5] = {0, 0, 0, 0, 0};
|
||||||
|
int eventCount = 0;
|
||||||
|
lower35 &= (1ULL << kLowerBits) - 1ULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < cellCount; ++i) {
|
||||||
|
if (cells[i].requirement != kSlime) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t output = static_cast<uint32_t>(
|
||||||
|
nextStateFromTerm(lower35, cells[i].term) >> 17
|
||||||
|
);
|
||||||
|
const uint32_t low17 = output & ((1U << 17) - 1U);
|
||||||
|
if ((low17 & 1U) != 0U) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t upper14 = output >> 17;
|
||||||
|
const uint32_t parity = upper14 & 1U;
|
||||||
|
const uint32_t v0 = (upper14 - parity) >> 1;
|
||||||
|
const uint32_t wanted = (3U * ((5U - ((low17 >> 1) % 5U) +
|
||||||
|
5U - parity) % 5U)) % 5U;
|
||||||
|
const uint32_t before = (wanted + 5U - (v0 % 5U)) % 5U;
|
||||||
|
UpperEvent event{
|
||||||
|
static_cast<uint16_t>(kUpperSize - v0),
|
||||||
|
static_cast<uint8_t>(before),
|
||||||
|
};
|
||||||
|
|
||||||
|
int insert = eventCount;
|
||||||
|
while (insert > 0 && events[insert - 1].threshold > event.threshold) {
|
||||||
|
events[insert] = events[insert - 1];
|
||||||
|
--insert;
|
||||||
|
}
|
||||||
|
events[insert] = event;
|
||||||
|
++eventCount;
|
||||||
|
++residueCounts[before];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t emitted = 0;
|
||||||
|
if (eventCount == 0) {
|
||||||
|
for (uint32_t n = 0; n < kUpperSize; ++n) {
|
||||||
|
const uint32_t m = (kHalfDeltaInverse * n) & kUpperMask;
|
||||||
|
const uint64_t seed = lower35 + (uint64_t(m) << kLowerBits);
|
||||||
|
if (verifyPattern(seed, cells, cellCount)) {
|
||||||
|
emit(seed);
|
||||||
|
++emitted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t start = 0;
|
||||||
|
int index = 0;
|
||||||
|
for (;;) {
|
||||||
|
const uint32_t stop = index < eventCount ? events[index].threshold : kUpperSize;
|
||||||
|
int commonResidue = -1;
|
||||||
|
for (int residue = 0; residue < 5; ++residue) {
|
||||||
|
if (residueCounts[residue] == eventCount) {
|
||||||
|
commonResidue = residue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commonResidue >= 0 && start < stop) {
|
||||||
|
for (uint32_t n = firstWithResidue(start, uint32_t(commonResidue));
|
||||||
|
n < stop;
|
||||||
|
n += 5U) {
|
||||||
|
const uint32_t m = (kHalfDeltaInverse * n) & kUpperMask;
|
||||||
|
const uint64_t seed = lower35 + (uint64_t(m) << kLowerBits);
|
||||||
|
if (verifyPattern(seed, cells, cellCount)) {
|
||||||
|
emit(seed);
|
||||||
|
++emitted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == eventCount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const uint32_t threshold = events[index].threshold;
|
||||||
|
while (index < eventCount && events[index].threshold == threshold) {
|
||||||
|
const uint32_t before = events[index].before;
|
||||||
|
--residueCounts[before];
|
||||||
|
++residueCounts[(before + 2U) % 5U];
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
start = threshold;
|
||||||
|
}
|
||||||
|
return emitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace slime_research
|
||||||
|
|
||||||
|
#undef SLIME_HD
|
||||||
|
#undef SLIME_INLINE
|
||||||
72
include/slime/upper_interval.hpp
Normal file
72
include/slime/upper_interval.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "solver_core.hpp"
|
||||||
|
|
||||||
|
#if defined(__CUDACC__)
|
||||||
|
#define FS_HD __host__ __device__
|
||||||
|
#define FS_INLINE __forceinline__
|
||||||
|
#else
|
||||||
|
#define FS_HD
|
||||||
|
#define FS_INLINE inline
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace full_search_experiment {
|
||||||
|
namespace sr = slime_research;
|
||||||
|
|
||||||
|
// Positive (slime) constraints only; positions may be sparse.
|
||||||
|
// Each surviving residue has a single interval. Keeping the first cell's
|
||||||
|
// two residues makes intersection constant-space. Check negative cells on emit.
|
||||||
|
template<class Emit>
|
||||||
|
FS_HD FS_INLINE void solveIntervals(uint64_t low, const sr::CellTerm* cells,
|
||||||
|
int count, Emit& emit) {
|
||||||
|
low &= (1ULL << 35) - 1;
|
||||||
|
uint32_t q = uint32_t(sr::nextStateFromTerm(low, cells[0].term) >> 17);
|
||||||
|
if (q & 1U) return;
|
||||||
|
uint32_t b = q >> 18;
|
||||||
|
uint32_t r = q & ((1U << 18) - 1);
|
||||||
|
uint32_t threshold = 8192 - b;
|
||||||
|
uint32_t residue[2] = {(r % 5 + 5 - b % 5) % 5, 0};
|
||||||
|
residue[1] = (residue[0] + 2) % 5;
|
||||||
|
uint32_t lo[2] = {0, threshold}, hi[2] = {threshold, 8192};
|
||||||
|
unsigned active = 0;
|
||||||
|
for (int k = 0; k < 2; ++k)
|
||||||
|
if (sr::firstWithResidue(lo[k], residue[k]) < hi[k]) active |= 1U << k;
|
||||||
|
for (int i = 1; i < count && active; ++i) {
|
||||||
|
q = uint32_t(sr::nextStateFromTerm(low, cells[i].term) >> 17);
|
||||||
|
if (q & 1U) return;
|
||||||
|
b = q >> 18;
|
||||||
|
r = q & ((1U << 18) - 1);
|
||||||
|
threshold = 8192 - b;
|
||||||
|
const uint32_t before = (r % 5 + 5 - b % 5) % 5;
|
||||||
|
const uint32_t after = (before + 2) % 5;
|
||||||
|
for (int k = 0; k < 2; ++k) if (active & (1U << k)) {
|
||||||
|
if (residue[k] == before) hi[k] = hi[k] < threshold ? hi[k] : threshold;
|
||||||
|
else if (residue[k] == after) lo[k] = lo[k] > threshold ? lo[k] : threshold;
|
||||||
|
else { active &= ~(1U << k); continue; }
|
||||||
|
if (sr::firstWithResidue(lo[k], residue[k]) >= hi[k]) active &= ~(1U << k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int k = 0; k < 2; ++k) if (active & (1U << k)) {
|
||||||
|
for (uint32_t t = sr::firstWithResidue(lo[k], residue[k]); t < hi[k]; t += 5) {
|
||||||
|
const uint64_t seed = low + (uint64_t((4965U * t) & 8191U) << 35);
|
||||||
|
if (sr::verifyPattern(seed, cells, count)) emit(seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A cheap necessary condition before either exact high-bit solver.
|
||||||
|
// Adding the common high-bit shift can wrap q_i-q_0 once: delta mod 10 is 0 or 8.
|
||||||
|
FS_HD FS_INLINE bool differenceFilter(uint64_t low, const sr::CellTerm* cells, int count) {
|
||||||
|
const uint32_t q0 = uint32_t(sr::nextStateFromTerm(low, cells[0].term) >> 17);
|
||||||
|
for (int i = 1; i < count; ++i) {
|
||||||
|
const uint32_t qi = uint32_t(sr::nextStateFromTerm(low, cells[i].term) >> 17);
|
||||||
|
const uint32_t delta = (qi - q0) & 0x7fffffffU;
|
||||||
|
const uint32_t r = delta % 10;
|
||||||
|
if (r != 0 && r != 8) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef FS_HD
|
||||||
|
#undef FS_INLINE
|
||||||
9
research/.gitignore
vendored
Normal file
9
research/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
slime_pattern_cpp
|
||||||
|
slime_pattern_cuda
|
||||||
|
slime_position_cuda
|
||||||
|
slime_position_residue_cuda
|
||||||
|
slime_bruteforce_cpp_bench
|
||||||
|
slime_bruteforce_cuda_bench
|
||||||
|
benchmark_results*.json
|
||||||
239
research/BENCHMARK.md
Normal file
239
research/BENCHMARK.md
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
# Fixed-position benchmark
|
||||||
|
|
||||||
|
Date: 2026-10-03
|
||||||
|
|
||||||
|
Hardware used for this run:
|
||||||
|
|
||||||
|
- GPU: NVIDIA GeForce RTX 3060 Laptop GPU, compute capability 8.6
|
||||||
|
- CPU: the current WSL host, using 16 worker threads
|
||||||
|
- CUDA toolkit: 12.0
|
||||||
|
|
||||||
|
## Workload
|
||||||
|
|
||||||
|
Find every lower-48 seed producing a solid 5x5 slime pattern whose top-left
|
||||||
|
chunk is `(51069, 419124)`. The known answer contains 24 seeds.
|
||||||
|
|
||||||
|
The new solver performs the complete workload and checks its final candidates
|
||||||
|
with Java-compatible `nextInt(10)`. Running a complete old-style `2^48` scan is
|
||||||
|
unnecessarily expensive, so the brute-force baselines scan large contiguous
|
||||||
|
ranges, report throughput, and linearly project the full-space time.
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
| Implementation | Measured work | Measured time | Projected/complete time | Speedup |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| Old brute-force CPU | `2^32` seeds | 1.851 s | 121,290 s (33.7 h) | 1x |
|
||||||
|
| New solver CPU | complete, 24/24 results | 0.61 s wall | 0.61 s | ~199,000x |
|
||||||
|
| Old brute-force CUDA | `2^36` seeds | 1.256 s | 5,145 s (85.7 min) | 1x |
|
||||||
|
| New solver CUDA | complete, 24/24 results | 59.6 ms kernel | 59.6 ms | ~86,300x |
|
||||||
|
| New solver CUDA, end-to-end | complete, 24/24 results | 0.36 s wall | 0.36 s | ~14,300x |
|
||||||
|
|
||||||
|
The end-to-end CUDA row includes process startup, CUDA context creation, CPU
|
||||||
|
enumeration of the 84 lower-18 roots, allocation, copies, kernel execution, and
|
||||||
|
readback. The kernel-only comparison isolates the algorithms on the GPU.
|
||||||
|
|
||||||
|
The new CUDA kernel is about 10.2x faster than the new 16-thread CPU solver for
|
||||||
|
this particular 5x5 position. The old brute-force CUDA baseline is about 23.6x
|
||||||
|
faster than its CPU counterpart.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make -C research
|
||||||
|
|
||||||
|
# New complete CPU solver
|
||||||
|
/usr/bin/time -f 'wall_seconds=%e' \
|
||||||
|
research/slime_pattern_cpp search \
|
||||||
|
--x 51069 --z 419124 -n 5 -p 16 --max-results 0
|
||||||
|
|
||||||
|
# Old-style CPU throughput
|
||||||
|
research/slime_bruteforce_cpp_bench \
|
||||||
|
--x 51069 --z 419124 -n 5 --count 4294967296 -p 16
|
||||||
|
|
||||||
|
# New complete CUDA solver
|
||||||
|
research/slime_pattern_cuda \
|
||||||
|
--x 51069 --z 419124 -n 5 --threads 256 --max-results 0
|
||||||
|
|
||||||
|
# Old-style CUDA throughput
|
||||||
|
research/slime_bruteforce_cuda_bench \
|
||||||
|
--x 51069 --z 419124 -n 5 --count 68719476736
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cross-check against the original root CUDA executable
|
||||||
|
|
||||||
|
The repository's original `slime-cuda` was also run directly over one billion
|
||||||
|
seeds with `-n 5 -d 25 -r 1`. It took 0.439 seconds. That program checks nine
|
||||||
|
top-left positions per seed, so it is not the same fixed-position workload.
|
||||||
|
Normalized as position/seed pairs it processed about 20.5 billion pairs/s,
|
||||||
|
whereas the dedicated old-style benchmark processed about 54.7 billion
|
||||||
|
seeds/s. Using the faster dedicated baseline therefore makes the reported
|
||||||
|
new-vs-old acceleration conservative.
|
||||||
|
|
||||||
|
## Interpretation
|
||||||
|
|
||||||
|
Most of the acceleration is algorithmic, not CUDA-specific:
|
||||||
|
|
||||||
|
- brute force visits all `2^48 = 281,474,976,710,656` seed values;
|
||||||
|
- the known position has only 84 viable lower-18 roots;
|
||||||
|
- those expand to 11,010,048 lower-35-bit values;
|
||||||
|
- each low-bit value's remaining 13 bits are solved by threshold/residue intersection,
|
||||||
|
rather than enumerating another 8192 values.
|
||||||
|
|
||||||
|
Results depend strongly on pattern selectivity. Sparse patterns and patterns
|
||||||
|
with few required-slime cells retain more roots and can produce many more upper
|
||||||
|
solutions, reducing the speedup. These numbers should not be extrapolated to
|
||||||
|
world-border position enumeration, which remains a separate problem.
|
||||||
|
|
||||||
|
## Expanded 64-position run
|
||||||
|
|
||||||
|
The initial result above is useful for correctness, but one known hit is not a
|
||||||
|
performance distribution. A second, deterministic stratified run therefore
|
||||||
|
used 64 positions:
|
||||||
|
|
||||||
|
- 32 positions at and around the known difficult coordinate, deliberately
|
||||||
|
retaining a range of nonzero lower-18 root counts;
|
||||||
|
- 32 positions covering the origin, four world-border corners, large signed
|
||||||
|
coordinates, and pseudorandom world-border positions (seed `20261004`).
|
||||||
|
|
||||||
|
For every position the new CPU and CUDA solvers completed the whole lower-48
|
||||||
|
search. The old CPU baseline actually checked `2^28` seeds per position and
|
||||||
|
the old CUDA baseline checked `2^34` per position. Aggregate measured old-style
|
||||||
|
work was therefore:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CPU: 64 * 2^28 = 17,179,869,184 seeds
|
||||||
|
CUDA: 64 * 2^34 = 1,099,511,627,776 seeds (2^40)
|
||||||
|
```
|
||||||
|
|
||||||
|
CPU and CUDA agreed on the result count for all 64 positions. Only the known
|
||||||
|
coordinate had matches (24); every other sampled position had zero.
|
||||||
|
|
||||||
|
### Baseline stability
|
||||||
|
|
||||||
|
Across the 64 coordinates, brute-force throughput was:
|
||||||
|
|
||||||
|
| Baseline | Mean | Median | Range | 95% CI of mean |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| CPU, 16 threads | 2.187B seeds/s | 2.171B | 1.665B–2.686B | 2.139B–2.235B |
|
||||||
|
| RTX 3060 CUDA | 53.81B seeds/s | 53.76B | 49.36B–58.13B | 53.46B–54.15B |
|
||||||
|
|
||||||
|
The CUDA coefficient of variation was only 2.6%, which supports linear
|
||||||
|
projection of its complete `2^48` time to roughly 87 minutes.
|
||||||
|
|
||||||
|
### Root and runtime distribution
|
||||||
|
|
||||||
|
The full set contained 42 positions with zero lower-18 roots and 22 with
|
||||||
|
nonzero roots. Zero-root positions are a real algorithmic result—the initial
|
||||||
|
`2^18` parity pass proves that their entire lower-48 space is impossible—but
|
||||||
|
they are excluded from the speedup table below to avoid inflating it.
|
||||||
|
|
||||||
|
For the 22 nonzero-root positions:
|
||||||
|
|
||||||
|
| Metric | Minimum | Median | Mean | P95 | Maximum |
|
||||||
|
|---|---:|---:|---:|---:|---:|
|
||||||
|
| lower-18 roots | 1 | 20 | 28.6 | 84 | 152 |
|
||||||
|
| lower-35-bit values | 131,072 | 2,621,440 | 3,753,425 | 11,010,048 | 19,922,944 |
|
||||||
|
| New CPU search time | 6.50 ms | 166.6 ms | 225.3 ms | 606.5 ms | 1.153 s |
|
||||||
|
| New CUDA kernel time | 1.64 ms | 14.61 ms | 20.85 ms | 60.81 ms | 107.6 ms |
|
||||||
|
| CPU algorithm speedup | 107,104x | 774,825x | 4,444,739x | 18,288,517x | 19,220,240x |
|
||||||
|
| CUDA algorithm speedup | 48,055x | 369,354x | 969,525x | 3,120,648x | 3,252,226x |
|
||||||
|
|
||||||
|
The deliberately hardest sampled position was `(51069, 419125)`: 152 roots,
|
||||||
|
19,922,944 lower-35-bit values, 1.153 seconds on CPU, and 107.6 ms on CUDA. Even
|
||||||
|
there, the conservative same-platform acceleration was 107,104x on CPU and
|
||||||
|
48,055x on CUDA.
|
||||||
|
|
||||||
|
The expanded run is reproducible with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 research/benchmark_suite.py \
|
||||||
|
--samples 64 \
|
||||||
|
--cpu-threads 16 \
|
||||||
|
--cpu-old-count 268435456 \
|
||||||
|
--cuda-old-count 17179869184 \
|
||||||
|
--cuda-threads 256 \
|
||||||
|
--output /tmp/slime_benchmark_64.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Times in this section intentionally compare the measured search/kernel regions.
|
||||||
|
CUDA process startup and context creation are not included; the earlier
|
||||||
|
single-position end-to-end row shows their effect. The upper solver still has
|
||||||
|
the documented eight-value Java rejection-tail completeness caveat.
|
||||||
|
|
||||||
|
## Coordinate-side outer-sum CUDA experiment
|
||||||
|
|
||||||
|
The follow-up coordinate scanner fixes world seed `61692151016558` and checks
|
||||||
|
an `8193 x 8193` rectangle containing the known 5x5 top-left position:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./research/slime_position_cuda --repeats 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Each invocation runs two kernels and requires identical sorted hit lists:
|
||||||
|
|
||||||
|
- direct: one CUDA thread per position, evaluating the full coordinate
|
||||||
|
polynomial as cells are needed;
|
||||||
|
- outer-sum: one 16x16 tile per CUDA block, sharing expanded `F(x+i)` and
|
||||||
|
`G(z+j)` terms across the tile.
|
||||||
|
|
||||||
|
On the same RTX 3060 Laptop GPU, median kernel times were:
|
||||||
|
|
||||||
|
| implementation | positions | kernel time | positions/s |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| direct | 67,125,249 | 1.608 ms | 41.75 billion |
|
||||||
|
| tiled outer-sum | 67,125,249 | 1.332 ms | 50.39 billion |
|
||||||
|
|
||||||
|
That is a 1.21x outer-sum speedup. Both paths found exactly one hit,
|
||||||
|
`(51069, 419124)`. Additional 4,004,001-position cross-checks at negative and
|
||||||
|
near-world-border coordinates agreed exactly and showed 1.30--1.31x speedups.
|
||||||
|
|
||||||
|
The direct rate is high because a random position usually fails after one or
|
||||||
|
two slime tests; it does not normally pay for all 25 cells. Consequently this
|
||||||
|
experiment measures the arithmetic/reuse benefit of outer-sum, not the hoped-for
|
||||||
|
algorithmic benefit of signature grouping. It still scans the Cartesian area
|
||||||
|
and therefore is not a feasible full-world-border method.
|
||||||
|
|
||||||
|
## Full-world fixed-seed residue filter
|
||||||
|
|
||||||
|
The next prototype uses the fact that the necessary first-output parity is
|
||||||
|
periodic in both coordinates modulo `2^18`. It checks all `2^36` residue pairs
|
||||||
|
on CUDA, expands survivors over the world border, and exactly verifies only
|
||||||
|
those expanded coordinates:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./research/slime_position_residue_cuda --seed 61692151016558
|
||||||
|
```
|
||||||
|
|
||||||
|
Results for four complete residue-space runs were:
|
||||||
|
|
||||||
|
| seed | residue kernel | survivors | expanded exact candidates | exact time | hits |
|
||||||
|
|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 61692151016558 | 1.931 s | 180,476 | 36,937,048 | 284 ms | 1 |
|
||||||
|
| 0 | 1.920 s | 195,672 | 40,046,949 | 352 ms | 0 |
|
||||||
|
| 1 | 1.935 s | 175,992 | 36,010,787 | 283 ms | 0 |
|
||||||
|
| 281474976710655 | 1.944 s | 181,276 | 37,098,020 | 313 ms | 0 |
|
||||||
|
|
||||||
|
The searched world rectangle contains 14,062,500,000,000 positions. For the
|
||||||
|
known seed, the necessary filter reduces the exact workload by about 381,000x.
|
||||||
|
Axis-table preparation took 224--289 ms, making total measured runtime roughly
|
||||||
|
2.5 seconds.
|
||||||
|
|
||||||
|
For a larger direct baseline, both rectangular kernels were run five times over
|
||||||
|
`2^34 = 17,179,869,184` positions. Median direct time was 524.1 ms (32.78
|
||||||
|
billion positions/s), while tiled outer-sum took 315.9 ms (54.38 billion/s).
|
||||||
|
Linear projection over the full rectangle is about 429 seconds direct or 259
|
||||||
|
seconds tiled, versus about 2.5 seconds for the residue pipeline. Thus the new
|
||||||
|
fixed-seed method is approximately 170x faster than direct and 100x faster than
|
||||||
|
the tiled Cartesian scan. The residue run itself actually processes all
|
||||||
|
`2^36` pairs; only the Cartesian full-world numbers are projections.
|
||||||
|
|
||||||
|
Like the seed-side prototype, the necessary parity filter omits the extremely
|
||||||
|
rare branch where Java rejects its first `next(31)` output. Exact verification
|
||||||
|
eliminates false positives but cannot restore a candidate already removed by
|
||||||
|
that filter.
|
||||||
|
|
||||||
|
## Retired joint-search experiments
|
||||||
|
|
||||||
|
X/Z independent filtering and its lower35 generation experiments were removed on 2026-10-11.
|
||||||
|
Their historical measurements and limitations are consolidated in
|
||||||
|
[X/Z 筛轴试错报告](XZ_FILTER_TRIAL_REPORT.md). No full-world joint speedup was established.
|
||||||
94
research/FEASIBILITY_REVIEW.md
Normal file
94
research/FEASIBILITY_REVIEW.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# 全世界联合搜索的可行性复查
|
||||||
|
|
||||||
|
后续已完成 CUDA、随机坐标批处理、坐标类复用及严格拒绝补扫实验,结论见
|
||||||
|
[全流程实验报告](experiments/full_search_feasibility/REPORT.md)。下文保留最初 CPU 探索记录;后续实测已替代其中尚待验证的 GPU 性能和覆盖结论。
|
||||||
|
|
||||||
|
日期:2026-10-11。此轮增加隔离的 CPU 实验,没有替换现有搜索入口。GPU 在当前环境中无法访问,以下新测量全部是 CPU 单线程,不能直接作为 CUDA 加速比。
|
||||||
|
|
||||||
|
## 1. 已验证:高 13 位改为逐格区间求交
|
||||||
|
|
||||||
|
固定 lower35 后,令 `t = A * upper13 mod 8192`。每格输出为
|
||||||
|
`q = r + 2^18 * ((b+t) mod 8192)`,其中 `r = q(0) mod 2^18`、`b = q(0) >> 18`。
|
||||||
|
若 r 为奇数则无首次输出候选;否则该格允许:
|
||||||
|
|
||||||
|
- `0 <= t < 8192-b`,`t mod 5 = (r-b) mod 5`;
|
||||||
|
- `8192-b <= t < 8192`,`t mod 5 = (r-b+2) mod 5`。
|
||||||
|
|
||||||
|
首格给出两个不同余数的区间。后续每格对每个现存余数最多允许其中一侧,所以求交后仍然最多两个区间,不需要排序 25 个事件,也不需要分裂出更多区间。任何时刻两个区间都没有整数解,就可以停止检查该 lower35。
|
||||||
|
|
||||||
|
锚点 `(51069,419124)`,84 个低 18 位根,完整检查 11,010,048 个 lower35,交替运行三轮:
|
||||||
|
|
||||||
|
| 单线程完整候选扫描 | 中位数 |
|
||||||
|
| --- | ---: |
|
||||||
|
| 现有排序事件求解器 | 5.34717 秒 |
|
||||||
|
| 两个区间逐格求交 | 0.239393 秒 |
|
||||||
|
| 同口径速度比 | 22.34 倍 |
|
||||||
|
|
||||||
|
两者每轮完整种子列表一致,均为 24 个。新方法平均每个 lower35 处理 2.58882 格。这是计算区间耗时,不包含低 18 位根生成及进程启动;两个版本采用相同计时范围。新版本还包含统计格子访问量的计数开销。
|
||||||
|
|
||||||
|
另外通过 20 个坐标、40,000 个 lower35 的旧/新结果列表对照,以及 128 个 lower35 平面的全部 8192 个高位直接枚举对照(含已知命中平面)。这支持算法替换的可行性,但尚未验证 CUDA 寄存器占用和实际吞吐。
|
||||||
|
|
||||||
|
## 2. 已验证:固定坐标可用逆推补齐拒绝重抽分支
|
||||||
|
|
||||||
|
Java 首次输出的拒绝区间为 `[2^31-8, 2^31)`。每个输出对应 `2^17` 个完整 LCG 状态,因此每格只有 `8 * 2^17 = 2^20` 个首次拒绝状态。
|
||||||
|
|
||||||
|
对每个这种推进后状态 `v`,逆推:
|
||||||
|
|
||||||
|
```
|
||||||
|
u = A_inverse * (v - 11) mod 2^48
|
||||||
|
world_seed = ((u xor C) - coordinate_term) mod 2^48
|
||||||
|
```
|
||||||
|
|
||||||
|
其中 `A_inverse=0xDFE05BCB1365`、`C=0x5E434E432`。再用完整 Java Random 语义检查全部 25 格。
|
||||||
|
|
||||||
|
**覆盖理由:** 任意真实解要么 25 格的首次输出都被接受,此时必在首次输出求解器的候选空间内;要么至少一格首次拒绝,此时必被该格的逆推枚举覆盖。即使后面继续拒绝,也由完整验证处理。因此两个集合取并集、去重,可构成固定坐标的严格完整搜索。
|
||||||
|
|
||||||
|
样例补扫 `25 * 2^20 = 26,214,400` 个候选,耗时 0.0968122 秒,未发现额外命中。约 0.239+0.097=0.336 秒是两个计算阶段之和,不是另行测得的端到端时间。现有正式入口尚未接入这个补扫。
|
||||||
|
|
||||||
|
## 3. 坐标分布:原来的已知命中点不代表平均坐标
|
||||||
|
|
||||||
|
确定性 PRNG 在世界区块范围 `[-1875000,1874999]` 内均匀抽取 4096 对坐标,实心 5×5:
|
||||||
|
|
||||||
|
- 3968 对(96.875%)没有首次输出低 18 位根;
|
||||||
|
- 根数平均 0.535645,中位数和 P95 均为 0,最大 156;
|
||||||
|
- 全部样本低位筛选耗时 4.17809 秒。
|
||||||
|
|
||||||
|
这里是随机抽样,不是对全世界比例的证明;首次输出无根也不排除拒绝重抽产生的解。用已知有 84 根的命中点乘上全世界坐标数,会明显高估该样本分布对应的平均候选工作量。不能把抽样均值或 22.34 倍局部加速直接宣布为全世界搜索总耗时。
|
||||||
|
|
||||||
|
## 4. 可用的全局预处理:归一化低位坐标类
|
||||||
|
|
||||||
|
令 `s=world_seed+T(x,z) mod 2^48`,把锚点坐标项吸收到有效种子。相对项的低 18 位为
|
||||||
|
|
||||||
|
```
|
||||||
|
Delta(i,j) = A_x*(2*x*i+i*i) + B_x*i
|
||||||
|
+ A_z*(2*z*j+j*j) + B_z*j (mod 2^18)
|
||||||
|
```
|
||||||
|
|
||||||
|
Java int 回绕及符号扩展不改变这条低 18 位等式。由于 `2*A_x` 含两个因子 2、`2*A_z` 含一个因子 2:
|
||||||
|
|
||||||
|
- x 的相对项低位周期为 `2^16`;
|
||||||
|
- z 的相对项低位周期为 `2^17`。
|
||||||
|
|
||||||
|
所以最多 `2^33 = 8,589,934,592` 类,每类可以共享有效种子的低位根表;世界范围平均每类约 1637 个锚点。另用 250,000 个格子的多项式计算核对了周期。
|
||||||
|
|
||||||
|
这只能减少低位根生成的重复,不能将该复用倍数乘到完整求解器上。更高位坐标项、Java 回绕产生的差异及世界种子标签仍须处理。也不能用首次输出无根表跳过拒绝重抽补扫。
|
||||||
|
|
||||||
|
## 5. 尚未解决的关键问题与下一步
|
||||||
|
|
||||||
|
目前有证据支持的落地顺序是:
|
||||||
|
|
||||||
|
1. 将两区间求交移植到 CUDA,并在随机坐标批次与已知命中坐标上比较完整结果、总时间。
|
||||||
|
2. 固定坐标入口接入拒绝尾部逆推补扫,建立严格完整的参考求解器。
|
||||||
|
3. 全局搜索先测低位坐标类批处理的建表成本、根分布和存储量,避免每坐标重启及重复根生成。
|
||||||
|
4. 研究在坐标类展开和中间位枚举之前应用多个内部格子的联合约束;必须测包含建表、候选生成、展开和验证的总成本。
|
||||||
|
|
||||||
|
第 4 项仍缺少已经验证的算法。之前独立筛轴过早生成大量 X/Z 配对的失败没有被以上局部优化消除。当前可以承诺验证、落地局部改进,不能承诺全世界全部种子目录可在几天或几周内完成。
|
||||||
|
|
||||||
|
## 复现
|
||||||
|
|
||||||
|
```
|
||||||
|
g++ -O3 -std=c++17 -march=native -Wall -Wextra research/upper_interval_probe.cpp -o /tmp/upper_interval_probe
|
||||||
|
/tmp/upper_interval_probe
|
||||||
|
```
|
||||||
|
|
||||||
|
代码:[upper_interval_probe.cpp](upper_interval_probe.cpp)。原始输出:[UPPER_INTERVAL_PROBE_RESULTS.txt](UPPER_INTERVAL_PROBE_RESULTS.txt)。
|
||||||
233
research/FFT_EXPERIMENT.md
Normal file
233
research/FFT_EXPERIMENT.md
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
# Fixed-seed CUDA FFT experiment (2026-10-07)
|
||||||
|
|
||||||
|
This is archived diagnostic code, not part of the active build. The independent
|
||||||
|
implementation follows the bitmap/correlation approach in the [public FFT
|
||||||
|
source](https://github.com/alchemyyy/UltraSlimeChunkFinder/blob/main/slime_fft.cu).
|
||||||
|
It uses our exact Java rejection-loop arithmetic, overlapping tiles, full-result
|
||||||
|
comparison with a direct GPU scan, and exact candidate validation. It handles
|
||||||
|
solid squares only; arbitrary-pattern extensions were deliberately deferred.
|
||||||
|
|
||||||
|
RTX 3060 Laptop GPU, seed 61692151016558, origin (46973,415028), 5x5,
|
||||||
|
one warm-up plus five measured repeats; table reports median device times.
|
||||||
|
Setup is separately timed and excluded from these device timings. Error reduction
|
||||||
|
uses warp shuffles, not a global atomic for every pixel.
|
||||||
|
|
||||||
|
| anchors | FFT tile | exact direct | FFT pipeline | FFT/direct |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| 67108864 | 2048 | 1.58822 ms | 22.4184 ms | 14.12x slower |
|
||||||
|
| 67108864 | 4096 | 1.42541 ms | 35.9567 ms | 25.23x slower |
|
||||||
|
| 268435456 | 2048 | 5.70470 ms | 71.2909 ms | 12.50x slower |
|
||||||
|
|
||||||
|
All three scans return exactly the known position (51069,419124). The existing
|
||||||
|
outer-sum scanner on the same 67108864-anchor region measured direct 1.38854 ms
|
||||||
|
and tiled 1.15098 ms: FFT/2048 is about 19.48x slower than tiled scanning.
|
||||||
|
Cold/reused cuFFT setup varied widely (about 7--388 ms), so steady-state
|
||||||
|
pipeline time is deliberately separated from setup cost.
|
||||||
|
|
||||||
|
Boundary tests also match the direct scanner: a known match starting at offset
|
||||||
|
(123,123) with tile=128 and step=124 crosses both tile seams; a 257-by-259
|
||||||
|
window places the match at the final valid anchor. The largest observed
|
||||||
|
rounding error in the final 5x5 runs was 1.33514e-5. A preliminary 8x8,
|
||||||
|
16777216-anchor run returned zero hits in both paths. These are measured
|
||||||
|
agreement checks, not a proof of numerical completeness for every input.
|
||||||
|
The candidate tolerance is 0.25; excessive observed error (>0.1) aborts.
|
||||||
|
|
||||||
|
Conclusion: early-exit exact scanning averages only about 1.11 cell checks per
|
||||||
|
anchor for a random all-slime pattern. Computing whole-grid FFT correlations
|
||||||
|
cannot compete here. This does not rule out FFT for non-slime-heavy patterns,
|
||||||
|
repeated convolutions on a reused bitmap, or other correlation workloads.
|
||||||
|
No FFT dependency or executable is retained in the default research build.
|
||||||
|
|
||||||
|
To reproduce, save the fenced source as `research/slime_fft_cuda.cu`, compile
|
||||||
|
with `nvcc -O3 -std=c++17 -o research/slime_fft_cuda research/slime_fft_cuda.cu -lcufft`,
|
||||||
|
and run with `--width 8192 --height 8192 --tile 2048 --repeats 5` (or dimensions
|
||||||
|
16384 by 16384). All memory capacities fail loudly instead of silently
|
||||||
|
truncating results.
|
||||||
|
|
||||||
|
## Archived source
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Independent fixed-seed FFT benchmark. Public approach:
|
||||||
|
// https://github.com/alchemyyy/UltraSlimeChunkFinder/blob/main/slime_fft.cu
|
||||||
|
#include "slime_solver_core.hpp"
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
#include <cufft.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr = slime_research;
|
||||||
|
void ck(cudaError_t e) { if (e != cudaSuccess) throw std::runtime_error(cudaGetErrorString(e)); }
|
||||||
|
void fk(cufftResult e) { if (e != CUFFT_SUCCESS) throw std::runtime_error("cuFFT error " + std::to_string(e)); }
|
||||||
|
struct Hit {
|
||||||
|
int x, z;
|
||||||
|
bool operator<(const Hit& b) const { return x != b.x ? x < b.x : z < b.z; }
|
||||||
|
bool operator==(const Hit& b) const { return x == b.x && z == b.z; }
|
||||||
|
};
|
||||||
|
constexpr unsigned capacity = 1U << 20;
|
||||||
|
__device__ bool solid(uint64_t seed, int x, int z, int n) {
|
||||||
|
for (int dx = 0; dx < n; ++dx)
|
||||||
|
for (int dz = 0; dz < n; ++dz)
|
||||||
|
if (!sr::exactIsSlimeFromTerm(seed, sr::coordinateTerm(x + dx, z + dz))) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
__device__ void emit(Hit* hits, unsigned* count, int x, int z) {
|
||||||
|
unsigned index = atomicAdd(count, 1U);
|
||||||
|
if (index < capacity) hits[index] = {x, z};
|
||||||
|
}
|
||||||
|
__global__ void direct(uint64_t seed, int x0, int z0, int w, int h, int n,
|
||||||
|
Hit* hits, unsigned* count) {
|
||||||
|
for (uint64_t i = uint64_t(blockIdx.x) * blockDim.x + threadIdx.x;
|
||||||
|
i < uint64_t(w) * h; i += uint64_t(gridDim.x) * blockDim.x) {
|
||||||
|
int x = x0 + i / h, z = z0 + i % h;
|
||||||
|
if (solid(seed, x, z, n)) emit(hits, count, x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__global__ void bitmap(uint64_t seed, float* image, int tile, int x0, int z0,
|
||||||
|
int validX, int validZ) {
|
||||||
|
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < tile * tile;
|
||||||
|
i += gridDim.x * blockDim.x) {
|
||||||
|
int dx = i / tile, dz = i % tile;
|
||||||
|
image[i] = dx < validX && dz < validZ &&
|
||||||
|
sr::exactIsSlimeFromTerm(seed, sr::coordinateTerm(x0 + dx, z0 + dz)) ? 1.f : 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__global__ void multiply(cufftComplex* a, const cufftComplex* b, int count) {
|
||||||
|
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < count; i += gridDim.x * blockDim.x) {
|
||||||
|
float ar = a[i].x, ai = a[i].y;
|
||||||
|
a[i] = {ar * b[i].x - ai * b[i].y, ar * b[i].y + ai * b[i].x};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__global__ void peaks(uint64_t seed, const float* correlation, int tile,
|
||||||
|
int x0, int z0, int w, int h, int n, Hit* hits,
|
||||||
|
unsigned* count, unsigned* errorBits) {
|
||||||
|
float maxError = 0;
|
||||||
|
for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < w * h; i += gridDim.x * blockDim.x) {
|
||||||
|
int dx = i / h, dz = i % h;
|
||||||
|
float value = correlation[dx * tile + dz] / float(tile * tile);
|
||||||
|
maxError = fmaxf(maxError, fabsf(value - roundf(value)));
|
||||||
|
if (value > float(n * n) - .25f && solid(seed, x0 + dx, z0 + dz, n))
|
||||||
|
emit(hits, count, x0 + dx, z0 + dz);
|
||||||
|
}
|
||||||
|
for (int offset = 16; offset; offset /= 2)
|
||||||
|
maxError = fmaxf(maxError, __shfl_down_sync(0xffffffffU, maxError, offset));
|
||||||
|
if ((threadIdx.x & 31) == 0) atomicMax(errorBits, __float_as_uint(maxError));
|
||||||
|
}
|
||||||
|
struct Args {
|
||||||
|
uint64_t seed = 61692151016558ULL;
|
||||||
|
int x0 = 46973, z0 = 415028, width = 8192, height = 8192, size = 5, tile = 2048, repeats = 3;
|
||||||
|
};
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
try {
|
||||||
|
Args a;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
std::string key = argv[i];
|
||||||
|
if (key == "--help") {
|
||||||
|
std::cout << "--seed N --x0 N --z0 N --width N --height N -n N --tile N --repeats N\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (++i == argc) throw std::runtime_error("missing argument");
|
||||||
|
std::string value = argv[i];
|
||||||
|
if (key == "--seed") a.seed = std::stoull(value) & sr::kMask48;
|
||||||
|
else if (key == "--x0") a.x0 = std::stoi(value);
|
||||||
|
else if (key == "--z0") a.z0 = std::stoi(value);
|
||||||
|
else if (key == "--width") a.width = std::stoi(value);
|
||||||
|
else if (key == "--height") a.height = std::stoi(value);
|
||||||
|
else if (key == "-n") a.size = std::stoi(value);
|
||||||
|
else if (key == "--tile") a.tile = std::stoi(value);
|
||||||
|
else if (key == "--repeats") a.repeats = std::stoi(value);
|
||||||
|
else throw std::runtime_error("unknown argument " + key);
|
||||||
|
}
|
||||||
|
if (a.size < 1 || a.size > 64 || a.tile < a.size || a.tile > 8192 ||
|
||||||
|
(a.tile & (a.tile - 1)) || a.width < 1 || a.height < 1 || a.repeats < 1 ||
|
||||||
|
int64_t(a.x0) + a.width + a.size - 2 > INT32_MAX ||
|
||||||
|
int64_t(a.z0) + a.height + a.size - 2 > INT32_MAX)
|
||||||
|
throw std::runtime_error("invalid dimensions or int32 coordinate overflow");
|
||||||
|
ck(cudaFree(nullptr));
|
||||||
|
auto setupStart = std::chrono::steady_clock::now();
|
||||||
|
int pixels = a.tile * a.tile, frequencies = a.tile * (a.tile / 2 + 1);
|
||||||
|
float *image, *correlation;
|
||||||
|
cufftComplex *spectrum, *pattern;
|
||||||
|
Hit* hits; unsigned *count, *errorBits;
|
||||||
|
ck(cudaMalloc(&image, pixels * sizeof(float)));
|
||||||
|
ck(cudaMalloc(&correlation, pixels * sizeof(float)));
|
||||||
|
ck(cudaMalloc(&spectrum, frequencies * sizeof(cufftComplex)));
|
||||||
|
ck(cudaMalloc(&pattern, frequencies * sizeof(cufftComplex)));
|
||||||
|
ck(cudaMalloc(&hits, capacity * sizeof(Hit)));
|
||||||
|
ck(cudaMalloc(&count, sizeof(unsigned)));
|
||||||
|
ck(cudaMalloc(&errorBits, sizeof(unsigned)));
|
||||||
|
cufftHandle forward, inverse;
|
||||||
|
fk(cufftPlan2d(&forward, a.tile, a.tile, CUFFT_R2C));
|
||||||
|
fk(cufftPlan2d(&inverse, a.tile, a.tile, CUFFT_C2R));
|
||||||
|
std::vector<float> stencil(pixels, 0.f);
|
||||||
|
for (int dx = 0; dx < a.size; ++dx)
|
||||||
|
for (int dz = 0; dz < a.size; ++dz)
|
||||||
|
stencil[((a.tile - dx) % a.tile) * a.tile + (a.tile - dz) % a.tile] = 1.f;
|
||||||
|
ck(cudaMemcpy(image, stencil.data(), pixels * sizeof(float), cudaMemcpyHostToDevice));
|
||||||
|
fk(cufftExecR2C(forward, image, pattern));
|
||||||
|
ck(cudaDeviceSynchronize());
|
||||||
|
double setupMs = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - setupStart).count();
|
||||||
|
auto readHits = [&]() {
|
||||||
|
unsigned total;
|
||||||
|
ck(cudaMemcpy(&total, count, sizeof(total), cudaMemcpyDeviceToHost));
|
||||||
|
if (total > capacity) throw std::runtime_error("hit capacity exceeded; reduce window");
|
||||||
|
std::vector<Hit> result(total);
|
||||||
|
if (total) ck(cudaMemcpy(result.data(), hits, total * sizeof(Hit), cudaMemcpyDeviceToHost));
|
||||||
|
std::sort(result.begin(), result.end());
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
cudaEvent_t begin, end;
|
||||||
|
ck(cudaEventCreate(&begin)); ck(cudaEventCreate(&end));
|
||||||
|
std::vector<float> directMs, fftMs;
|
||||||
|
std::vector<Hit> reference;
|
||||||
|
float maxError = 0;
|
||||||
|
for (int repeat = -1; repeat < a.repeats; ++repeat) {
|
||||||
|
ck(cudaMemset(count, 0, sizeof(unsigned)));
|
||||||
|
ck(cudaEventRecord(begin));
|
||||||
|
int blocks = static_cast<int>(std::min<uint64_t>(65535, (uint64_t(a.width) * a.height + 255) / 256));
|
||||||
|
direct<<<blocks, 256>>>(a.seed, a.x0, a.z0, a.width, a.height, a.size, hits, count);
|
||||||
|
ck(cudaGetLastError()); ck(cudaEventRecord(end)); ck(cudaEventSynchronize(end));
|
||||||
|
float elapsed; ck(cudaEventElapsedTime(&elapsed, begin, end));
|
||||||
|
if (repeat >= 0) directMs.push_back(elapsed);
|
||||||
|
reference = readHits();
|
||||||
|
ck(cudaMemset(count, 0, sizeof(unsigned)));
|
||||||
|
ck(cudaMemset(errorBits, 0, sizeof(unsigned)));
|
||||||
|
ck(cudaEventRecord(begin));
|
||||||
|
int step = a.tile - a.size + 1;
|
||||||
|
for (int x = 0; x < a.width; x += step) for (int z = 0; z < a.height; z += step) {
|
||||||
|
int w = std::min(step, a.width - x), h = std::min(step, a.height - z);
|
||||||
|
bitmap<<<std::min(4096, (pixels + 255) / 256), 256>>>(a.seed, image, a.tile, a.x0 + x, a.z0 + z, w + a.size - 1, h + a.size - 1);
|
||||||
|
fk(cufftExecR2C(forward, image, spectrum));
|
||||||
|
multiply<<<std::min(4096, (frequencies + 255) / 256), 256>>>(spectrum, pattern, frequencies);
|
||||||
|
fk(cufftExecC2R(inverse, spectrum, correlation));
|
||||||
|
peaks<<<std::min(4096, (w * h + 255) / 256), 256>>>(a.seed, correlation, a.tile, a.x0 + x, a.z0 + z, w, h, a.size, hits, count, errorBits);
|
||||||
|
}
|
||||||
|
ck(cudaGetLastError()); ck(cudaEventRecord(end)); ck(cudaEventSynchronize(end));
|
||||||
|
ck(cudaEventElapsedTime(&elapsed, begin, end));
|
||||||
|
if (repeat >= 0) fftMs.push_back(elapsed);
|
||||||
|
float error; ck(cudaMemcpy(&error, errorBits, sizeof(error), cudaMemcpyDeviceToHost));
|
||||||
|
maxError = std::max(maxError, error);
|
||||||
|
if (error > .1f) throw std::runtime_error("FFT numeric error too large");
|
||||||
|
if (readHits() != reference) throw std::runtime_error("FFT disagrees with exact direct scan");
|
||||||
|
}
|
||||||
|
std::sort(directMs.begin(), directMs.end()); std::sort(fftMs.begin(), fftMs.end());
|
||||||
|
float d = directMs[directMs.size() / 2], f = fftMs[fftMs.size() / 2];
|
||||||
|
std::cout << "positions=" << uint64_t(a.width) * a.height << " size=" << a.size
|
||||||
|
<< " tile=" << a.tile << " hits=" << reference.size()
|
||||||
|
<< " direct_ms=" << d << " fft_ms=" << f << " speedup=" << d / f
|
||||||
|
<< " fft_setup_ms=" << setupMs << " max_rounding_error=" << maxError << '\n';
|
||||||
|
for (auto hit : reference) std::cout << "hit " << hit.x << ' ' << hit.z << '\n';
|
||||||
|
cufftDestroy(forward); cufftDestroy(inverse);
|
||||||
|
cudaEventDestroy(begin); cudaEventDestroy(end);
|
||||||
|
cudaFree(image); cudaFree(correlation); cudaFree(spectrum); cudaFree(pattern);
|
||||||
|
cudaFree(hits); cudaFree(count); cudaFree(errorBits);
|
||||||
|
return 0;
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
std::cerr << "error: " << error.what() << '\n'; return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
38
research/Makefile
Normal file
38
research/Makefile
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
CXX ?= g++
|
||||||
|
NVCC ?= nvcc
|
||||||
|
|
||||||
|
CXXFLAGS ?= -O3 -std=c++17 -march=native -pthread -Wall -Wextra -Wpedantic
|
||||||
|
NVCCFLAGS ?= -O3 -std=c++17
|
||||||
|
|
||||||
|
CORE_HEADERS := slime_solver_core.hpp ../include/slime/solver_core.hpp
|
||||||
|
|
||||||
|
.PHONY: all clean test
|
||||||
|
|
||||||
|
all: slime_pattern_cpp slime_pattern_cuda slime_position_cuda slime_position_residue_cuda slime_bruteforce_cpp_bench slime_bruteforce_cuda_bench
|
||||||
|
|
||||||
|
slime_pattern_cpp: slime_pattern_cpp.cpp $(CORE_HEADERS)
|
||||||
|
$(CXX) $(CXXFLAGS) -o $@ slime_pattern_cpp.cpp
|
||||||
|
|
||||||
|
slime_pattern_cuda: slime_pattern_cuda.cu $(CORE_HEADERS)
|
||||||
|
$(NVCC) $(NVCCFLAGS) -o $@ slime_pattern_cuda.cu
|
||||||
|
|
||||||
|
slime_position_cuda: slime_position_cuda.cu $(CORE_HEADERS)
|
||||||
|
$(NVCC) $(NVCCFLAGS) -o $@ slime_position_cuda.cu
|
||||||
|
|
||||||
|
slime_position_residue_cuda: slime_position_residue_cuda.cu $(CORE_HEADERS)
|
||||||
|
$(NVCC) $(NVCCFLAGS) -o $@ slime_position_residue_cuda.cu
|
||||||
|
|
||||||
|
slime_bruteforce_cpp_bench: slime_bruteforce_cpp_bench.cpp $(CORE_HEADERS)
|
||||||
|
$(CXX) $(CXXFLAGS) -o $@ slime_bruteforce_cpp_bench.cpp
|
||||||
|
|
||||||
|
slime_bruteforce_cuda_bench: slime_bruteforce_cuda_bench.cu $(CORE_HEADERS)
|
||||||
|
$(NVCC) $(NVCCFLAGS) -o $@ slime_bruteforce_cuda_bench.cu
|
||||||
|
|
||||||
|
test: slime_pattern_cpp
|
||||||
|
python3 -m unittest discover -s . -p 'test_*.py'
|
||||||
|
./slime_pattern_cpp verify --seed 61692151016558 --x 51069 --z 419124 -n 5
|
||||||
|
./slime_pattern_cpp roots --x 51069 --z 419124 -n 5 --bits 24
|
||||||
|
./slime_pattern_cpp upper --lower35 16420645998 --x 51069 --z 419124 -n 5
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f slime_pattern_cpp slime_pattern_cuda slime_position_cuda slime_position_residue_cuda slime_bruteforce_cpp_bench slime_bruteforce_cuda_bench
|
||||||
77
research/OPTIMIZATION_SUMMARY.md
Normal file
77
research/OPTIMIZATION_SUMMARY.md
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
# 阶段结论与部署前摘要
|
||||||
|
|
||||||
|
更新日期:2026-10-11。本文汇总当前保留的有意义优化,作为下一阶段部署的交接记录。
|
||||||
|
数据来自已有实验记录,本次没有重新运行性能测试。详细测量、基线与复现命令见
|
||||||
|
[BENCHMARK.md](BENCHMARK.md),程序使用方式见 [README.md](README.md)。
|
||||||
|
|
||||||
|
## 总体结论
|
||||||
|
|
||||||
|
最值得部署的是两个独立入口:**固定坐标反查种子**、**固定种子搜索世界坐标**。
|
||||||
|
X/Z 独立筛轴联合搜索路线已移除,历史测量见 [试错报告](XZ_FILTER_TRIAL_REPORT.md)。
|
||||||
|
|
||||||
|
全种子 × 全世界搜索仍未解决。现有固定坐标流程仍枚举中间 17 位。
|
||||||
|
|
||||||
|
后续独立实验已验证输出差过滤+区间求交的 CUDA 搜索 kernel 提速约 197~292 倍,
|
||||||
|
并实现完整拒绝重抽补扫。65536 随机坐标的同批完整流程吞吐外推全世界仍约 16 年,
|
||||||
|
所以仍不建议启动全量目录生成。实验没有替换正式入口,数据和边界见
|
||||||
|
[全流程实验报告](experiments/full_search_feasibility/REPORT.md)。
|
||||||
|
|
||||||
|
## 1. 固定坐标反查种子:主要算法收益
|
||||||
|
|
||||||
|
实现:`slime_pattern_cpp.cpp`、`slime_pattern_cuda.cu`,共享
|
||||||
|
`slime_solver_core.hpp`。
|
||||||
|
|
||||||
|
- 低 18 位必要条件过滤。已知 5×5 样例保留 84 个 roots。
|
||||||
|
- 每个 root 仍枚举中间 17 位,样例合计 11,010,048 个 lower35。
|
||||||
|
- 上 13 位使用阈值与模 5 约束解析求解,不逐个枚举 8192 个值。
|
||||||
|
- C++ 多线程和 CUDA 均已实现;最终候选执行准确的 Java 验证。
|
||||||
|
|
||||||
|
已知样例 CPU 约 0.61 秒;RTX 3060 Laptop 上 CUDA kernel 约 59.6 毫秒,
|
||||||
|
包含启动、分配、传输等开销约 0.36 秒。相对旧式 CUDA 暴力扫描,kernel
|
||||||
|
约快 86,300 倍,端到端约快 14,300 倍。旧算法完整 `2^48` 耗时是根据
|
||||||
|
大规模实测吞吐量外推,并非实际跑完。另有 64 个坐标的 CPU/CUDA 对照实验。
|
||||||
|
|
||||||
|
这些结果只属于固定坐标任务,不代表全种子、全世界联合搜索的加速比。
|
||||||
|
所谓遍历固定坐标搜索空间,仍受下文拒绝尾部候选覆盖限制。
|
||||||
|
|
||||||
|
## 2. 固定种子搜索世界坐标:主要 CUDA 收益
|
||||||
|
|
||||||
|
实现:`slime_position_residue_cuda.cu`。
|
||||||
|
|
||||||
|
先搜索低位坐标余数对,展开通过的具体坐标,再做精确 Java 验证。
|
||||||
|
已知种子实验展开 36,937,048 个具体位置,全流程约 2.5 秒。
|
||||||
|
相对直接扫描完整范围的外推时间约快 170 倍;四个种子已有实验记录。
|
||||||
|
这是目前坐标侧最值得保留的 CUDA 优化。
|
||||||
|
|
||||||
|
## 3. 较小但仍保留的收益
|
||||||
|
|
||||||
|
`slime_position_cuda.cu` 的 tiled outer-sum 共享 X/Z 坐标项,减少重复计算。
|
||||||
|
67,125,249 个位置的实验中,直接扫描 1.608 毫秒,outer-sum 1.332 毫秒,
|
||||||
|
约改善 1.21 倍。它适合作为直接扫描基线,优先级低于余数过滤,不解决全局规模。
|
||||||
|
|
||||||
|
## 4. 已停止的路线与边界
|
||||||
|
|
||||||
|
X/Z 独立筛轴、坐标类、联合诊断及中间位索引代码已删除,原因与历史验证汇总于
|
||||||
|
[X/Z 筛轴试错报告](XZ_FILTER_TRIAL_REPORT.md)。FFT 的独立试验见
|
||||||
|
[FFT_EXPERIMENT.md](FFT_EXPERIMENT.md)。
|
||||||
|
|
||||||
|
保留求解器仍使用首次输出候选模型,可能漏掉依赖 Java `nextInt(10)` 拒绝后重抽的解。
|
||||||
|
最终验证能排除误报,不能恢复漏解,不应称为严格穷尽目录生成器。
|
||||||
|
|
||||||
|
## 普通分支后续实验:16 候选成组筛选(2026-10-11)
|
||||||
|
|
||||||
|
[实验报告](experiments/normal_search/REPORT.md):利用中间位异或常量的四个连续零位,
|
||||||
|
每根将 131,072 个逐候选筛选改为 8,192 个组 mask。两个独立 65,536 坐标批次
|
||||||
|
搜索阶段(含新增预计算)相对上一轮最佳实现加速 6.51× / 6.56×,完整结果一致。
|
||||||
|
若仍逐坐标生成低位根,根生成加搜索的 kernel 时间之和仅改善约 1.38×。
|
||||||
|
只按搜索吞吐、乐观复用根的全世界外推约 50 天,未实测完整全局调度,未包含补扫。
|
||||||
|
独立实验已实现,正式入口未替换。
|
||||||
|
|
||||||
|
## 普通分支调度实验:复用坐标类根(2026-10-11)
|
||||||
|
|
||||||
|
[批次流程报告](experiments/normal_search/PIPELINE_REPORT.md):已实现每类生成一次根、
|
||||||
|
GPU 展开具体根任务,以及只为有根类构造/传输具体位置。全部展开根列表和命中列表一致。
|
||||||
|
512 随机类 / 32768 位置批次相对上一轮逐位置根生成+成组筛选,从 141.138 ms 降至
|
||||||
|
9.586 ms(14.72×),包含分组、展开、必要传输和回传,排除一次初始化。
|
||||||
|
全部有根类的压力批次只改善约 1.35×,没有重复类的散点反而慢约 8.9%。
|
||||||
|
之前约 50 天外推已假设复用根,不能再乘本轮加速倍数。正式入口仍未替换。
|
||||||
202
research/README.md
Normal file
202
research/README.md
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
# Slime pattern reverse-search prototype
|
||||||
|
|
||||||
|
当前范围测速 CLI 见 [bench/README.md](../bench/README.md),共用算法实现见 [include/](../include/README.md)。本目录保留研究入口与历史路径;近期实验见 [实验导航](experiments/README.md)。
|
||||||
|
|
||||||
|
This directory retains fixed-coordinate seed solvers and fixed-seed coordinate scanners.
|
||||||
|
It is not a complete all-seed world-border catalog generator.
|
||||||
|
|
||||||
|
- `slime_pattern_prototype.py`: readable reference/oracle.
|
||||||
|
- `slime_pattern_cpp.cpp`, `slime_pattern_cuda.cu`: fixed-coordinate seed search.
|
||||||
|
- `slime_position_cuda.cu`: direct vs tiled outer-sum coordinate scans.
|
||||||
|
- `slime_position_residue_cuda.cu`: fixed-seed coordinate-residue filtering.
|
||||||
|
- `slime_bruteforce_cpp_bench.cpp`, `slime_bruteforce_cuda_bench.cu`: baseline benchmarks.
|
||||||
|
- `slime_solver_core.hpp`: shared arithmetic and upper13 solver.
|
||||||
|
|
||||||
|
See [优化摘要](OPTIMIZATION_SUMMARY.md) and [BENCHMARK.md](BENCHMARK.md).
|
||||||
|
The independent X/Z filter/join route and its middle-bit experiments have been removed;
|
||||||
|
method, historical results and stopping reasons are consolidated in
|
||||||
|
[X/Z 筛轴试错报告](XZ_FILTER_TRIAL_REPORT.md).
|
||||||
|
|
||||||
|
## What is implemented
|
||||||
|
|
||||||
|
- Minecraft/Java-compatible coordinate overflow and `Random.nextInt(10)`
|
||||||
|
verification.
|
||||||
|
- Arbitrary patterns using `#` (slime), `.` (non-slime), and `?` (ignored).
|
||||||
|
- Lower-seed 2-adic lifting from 18 bits toward the 35-bit boundary.
|
||||||
|
- The analytic `35 + 13` split. For `seed = lower35 + m * 2^35`, all pattern
|
||||||
|
cells share the same upper-state displacement. Substituting
|
||||||
|
`n = 1645*m mod 8192` turns each required-slime cell into a piecewise
|
||||||
|
`n mod 5` constraint with one threshold. Sorting those thresholds solves all
|
||||||
|
upper 13 bits without scanning 8192 values.
|
||||||
|
- The position outer-sum identity modulo `2^32`, used to reuse coordinate
|
||||||
|
arithmetic in fixed-seed scans.
|
||||||
|
- A full-world fixed-seed position search: filter the `2^36` pairs
|
||||||
|
`(x mod 2^18, z mod 2^18)` on CUDA, expand only surviving residue classes,
|
||||||
|
then perform exact Java verification.
|
||||||
|
|
||||||
|
## Run it
|
||||||
|
|
||||||
|
The known public 5x5 sample is:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 research/slime_pattern_prototype.py verify \
|
||||||
|
--seed 61692151016558 --x 51069 --z 419124 -n 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Recover the 12 upper-bit solutions belonging to one known lower-35-bit value:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 research/slime_pattern_prototype.py upper \
|
||||||
|
--lower35 16420645998 --x 51069 --z 419124 -n 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Show the 18-to-24-bit lifting counts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 research/slime_pattern_prototype.py roots \
|
||||||
|
--x 51069 --z 419124 -n 5 --bits 24
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a custom pattern (rows are separated by `/`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 research/slime_pattern_prototype.py verify \
|
||||||
|
--seed 61692151016558 --x 51069 --z 419124 \
|
||||||
|
--pattern '##?/..#/#?#'
|
||||||
|
```
|
||||||
|
|
||||||
|
Check and print the outer-sum position signature:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 research/slime_pattern_prototype.py signature \
|
||||||
|
--x 51069 --z 419124 -n 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m unittest discover -s research -p 'test_*.py'
|
||||||
|
```
|
||||||
|
|
||||||
|
## C++ and CUDA
|
||||||
|
|
||||||
|
Build both native implementations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make -C research
|
||||||
|
```
|
||||||
|
|
||||||
|
The CPU CLI exposes the same important stages as the Python oracle:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
research/slime_pattern_cpp verify \
|
||||||
|
--seed 61692151016558 --x 51069 --z 419124 -n 5
|
||||||
|
|
||||||
|
research/slime_pattern_cpp roots \
|
||||||
|
--x 51069 --z 419124 -n 5 --bits 24
|
||||||
|
|
||||||
|
research/slime_pattern_cpp upper \
|
||||||
|
--lower35 16420645998 --x 51069 --z 419124 -n 5
|
||||||
|
```
|
||||||
|
|
||||||
|
`search` performs the complete fixed-position pipeline. For the public 5x5
|
||||||
|
sample it expands 84 lower-18 roots into 11,010,048 lower-35-bit values and
|
||||||
|
recovers all 24 known seeds:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
research/slime_pattern_cpp search \
|
||||||
|
--x 51069 --z 419124 -n 5 -p 16 --max-results 32
|
||||||
|
```
|
||||||
|
|
||||||
|
On the development machine this CPU run took about 0.68 seconds with 16 worker
|
||||||
|
threads. Treat that as a local sanity benchmark, not a portable performance
|
||||||
|
claim.
|
||||||
|
|
||||||
|
The CUDA program performs the same complete fixed-position search and accepts
|
||||||
|
the same compact arbitrary-pattern syntax:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
research/slime_pattern_cuda \
|
||||||
|
--x 51069 --z 419124 -n 5 --threads 128 --max-results 32
|
||||||
|
|
||||||
|
research/slime_pattern_cuda \
|
||||||
|
--x 0 --z 0 --pattern '##?/..#/#?#' --threads 128
|
||||||
|
```
|
||||||
|
|
||||||
|
CUDA 12 compilation was verified, and the solver was subsequently benchmarked
|
||||||
|
on an RTX 3060 Laptop GPU. See [BENCHMARK.md](BENCHMARK.md) for the old/new CPU
|
||||||
|
and CUDA comparison, methodology, commands, and limitations. GPU execution
|
||||||
|
from a sandboxed session may still require host GPU permission.
|
||||||
|
|
||||||
|
## Coordinate-side outer-sum experiment
|
||||||
|
|
||||||
|
For a fixed world seed, compare a direct position scan against a tiled
|
||||||
|
outer-sum scan over an inclusive rectangle:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
research/slime_position_cuda \
|
||||||
|
--seed 61692151016558 \
|
||||||
|
--x0 46973 --x1 55165 --z0 415028 --z1 423220 \
|
||||||
|
-n 5 --repeats 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Both kernels run on every invocation. Their sorted hit lists must be exactly
|
||||||
|
equal or the program fails. The direct kernel recomputes the full coordinate
|
||||||
|
term at each tested cell. The outer kernel assigns a 16x16 position tile to a
|
||||||
|
CUDA block and shares the expanded `F(x+i)` and `G(z+j)` axis arrays across the
|
||||||
|
block. On the development RTX 3060 Laptop GPU, the 67,125,249-position command
|
||||||
|
above took 1.608 ms direct versus 1.332 ms outer-sum (median of five), a 1.21x
|
||||||
|
kernel speedup, and found only `(51069, 419124)`.
|
||||||
|
|
||||||
|
This result is intentionally modest. A solid pattern rejects almost every
|
||||||
|
position on its first one or two chunks, so the direct kernel seldom evaluates
|
||||||
|
all 25 coordinate polynomials. Outer-sum removes duplicated coordinate work,
|
||||||
|
but does not change the asymptotic rectangle area. It remains a fixed-seed arithmetic-reuse baseline; full seed/coordinate catalog generation is unresolved.
|
||||||
|
|
||||||
|
## Full-world fixed-seed residue search
|
||||||
|
|
||||||
|
The low output parity used by the seed lifting depends on coordinates only
|
||||||
|
modulo `2^18`. The residue solver checks that fixed `2^36` space once, then
|
||||||
|
expands the surviving residue pairs into the requested world rectangle:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
research/slime_position_residue_cuda --seed 61692151016558 -n 5
|
||||||
|
```
|
||||||
|
|
||||||
|
The default bounds are `[-1875000, 1874999]` on both axes. For the public seed
|
||||||
|
on the development RTX 3060 Laptop GPU, the residue kernel took 1.93 seconds,
|
||||||
|
retained 180,476 of 68,719,476,736 residue pairs, and expanded them into
|
||||||
|
36,937,048 exact candidates out of 14,062,500,000,000 world positions. Exact
|
||||||
|
CPU verification took another 284 ms and found only `(51069, 419124)`.
|
||||||
|
|
||||||
|
Across four tested seeds, the kernel stayed between 1.92 and 1.94 seconds and
|
||||||
|
retained 175,992--195,672 residue pairs. Including axis-table preparation and
|
||||||
|
exact verification, a full run took about 2.5 seconds. A measured `2^34`
|
||||||
|
direct-position CUDA run projects to about 429 seconds over the same world, so
|
||||||
|
the residue method is about 170x faster end-to-end for this fixed-seed 5x5
|
||||||
|
workload.
|
||||||
|
|
||||||
|
## Removed experiments
|
||||||
|
|
||||||
|
Failed and inferior approaches are not part of the active build. Their data,
|
||||||
|
failure reasons, and key implementation snippets are preserved in
|
||||||
|
[`REJECTED_EXPERIMENTS.md`](REJECTED_EXPERIMENTS.md).
|
||||||
|
|
||||||
|
## Prototype limitations
|
||||||
|
|
||||||
|
1. The upper-bit candidate generator describes the first `next(31)` draw.
|
||||||
|
Java rejects only the final eight of the `2^31` possible values before
|
||||||
|
drawing again. Every emitted seed is checked with the exact rejection loop,
|
||||||
|
so there are no false positives, but a solution whose first draw is rejected
|
||||||
|
and whose later draw succeeds can be missed. An exhaustive catalog builder
|
||||||
|
must add a tiny exceptional-state branch.
|
||||||
|
2. Ordinary lifting is weak after the output parity becomes known: modulo 5 can
|
||||||
|
still be repaired by unknown binary bits. A solid 5x5 sample has 84 roots at
|
||||||
|
18 bits and 5,376 at 24 bits, then grows to roughly 11 million lower-35
|
||||||
|
low-bit values. The CLI therefore has a candidate safety limit.
|
||||||
|
3. Full-world position enumeration is implemented only for a fixed seed and a
|
||||||
|
solid square of size 1--8. Joint unknown `(seed, x, z)` catalog generation
|
||||||
|
is still unresolved; the X/Z independent-filter route was retired; see the trial report.
|
||||||
|
4. The residue parity filter shares the first-draw rejection-tail caveat from
|
||||||
|
item 1. Exact verification prevents false positives, but the rare solution
|
||||||
|
whose first draw is rejected can be filtered out before verification.
|
||||||
8
research/REJECTED_EXPERIMENTS.md
Normal file
8
research/REJECTED_EXPERIMENTS.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 已停止的实验
|
||||||
|
|
||||||
|
X/Z 独立筛轴及所有相关联合搜索、低 35 位生成尝试已移除。
|
||||||
|
方法、历史测量、正确性边界与失败原因统一保存在
|
||||||
|
[X/Z 筛轴试错报告](XZ_FILTER_TRIAL_REPORT.md)。
|
||||||
|
|
||||||
|
固定种子扫描的 FFT 实验独立保存在 [FFT_EXPERIMENT.md](FFT_EXPERIMENT.md):
|
||||||
|
它在测试中比直接扫描慢约 12.5~25.2 倍,活跃构建不依赖 cuFFT。
|
||||||
10
research/UPPER_INTERVAL_PROBE_RESULTS.txt
Normal file
10
research/UPPER_INTERVAL_PROBE_RESULTS.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
round=0 mode=0 seconds=5.31653 results=24 avg_cells=0
|
||||||
|
round=0 mode=1 seconds=0.253384 results=24 avg_cells=2.58882
|
||||||
|
round=1 mode=0 seconds=5.36552 results=24 avg_cells=0
|
||||||
|
round=1 mode=1 seconds=0.238491 results=24 avg_cells=2.58882
|
||||||
|
round=2 mode=0 seconds=5.34717 results=24 avg_cells=0
|
||||||
|
round=2 mode=1 seconds=0.239393 results=24 avg_cells=2.58882
|
||||||
|
differential_cases=40000 passed
|
||||||
|
direct_upper_cases=128 passed
|
||||||
|
uniform_positions=4096 zero=3968 roots_mean=0.535645 roots_median=0 roots_p95=0 roots_max=156 seconds=4.17809
|
||||||
|
rejection_tail_candidates=26214400 exact_unique_matches=0 seconds=0.0968122
|
||||||
109
research/XZ_FILTER_TRIAL_REPORT.md
Normal file
109
research/XZ_FILTER_TRIAL_REPORT.md
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
# X/Z 独立筛轴路线:试错报告
|
||||||
|
|
||||||
|
归档日期:2026-10-11。此路线已停止维护,相关实现、教学快照、测试、测速脚本与原始结果已删除。本文保留方法、历史测量和失败原因;数字来自此前实验,本次清理没有重跑性能测试,也不再提供复现入口。
|
||||||
|
|
||||||
|
## 结论与目标
|
||||||
|
|
||||||
|
目标是搜索全部有效种子与世界边界内全部坐标,找出实心 5×5 史莱姆集群。当前 X/Z 独立筛轴路线未达到这个目标:它能降低给定低 35 位时的坐标搜索代价,但无法以可承受的总成本生成全部相关低 35 位及坐标组合。
|
||||||
|
|
||||||
|
因此停止这条路线,保留固定坐标反查种子、固定种子扫描坐标和高 13 位解析求解。局部实验确实加速,不等于全局方案成立;失败结论针对已尝试的算法,不构成所有 X/Z 分解算法不可能的证明。
|
||||||
|
|
||||||
|
## 原始想法
|
||||||
|
|
||||||
|
将坐标项写为 `T(x,z)=F(x)+G(z)`,吸收锚点项,定义有效种子
|
||||||
|
`s=(world_seed+T(x,z)) mod 2^48`。图案格子的相对项为
|
||||||
|
`F(x+dx)-F(x)+G(z+dz)-G(z)`。
|
||||||
|
|
||||||
|
给定 `s mod 2^35`,横轴五格只依赖 X,纵轴五格只依赖 Z。分别计算两侧允许的高 13 位集合,丢弃空集合,再连接存活轴并求集合交集;检查内部格子和完整 25 格后,将有效种子平移回世界种子。
|
||||||
|
|
||||||
|
世界种子与有效种子不能混用。固定世界种子扫坐标时,有效种子的低 35 位随锚点变化,不能为整个世界使用一个统一平面。
|
||||||
|
|
||||||
|
## 局部成功与适用范围
|
||||||
|
|
||||||
|
历史机器为 AMD Ryzen 7 5800H;CPU 原型使用 g++ 13.3.0、`-O3 -std=c++17 -march=native`。下列实验任务与基线不同,速度比不能相乘。
|
||||||
|
|
||||||
|
| 实验 | 历史结果 | 未覆盖的工作 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 给定有效 lower35,完整低位轴周期 | 65,536 个 X × 131,072 个 Z 中,32 个 X、68 个 Z 存活;只连接 2,176 对。首次样例轴构建约 11.25 ms、融合连接约 1.05 ms | 供给 lower35 的成本与其他具体坐标展开 |
|
||||||
|
| 给定有效 lower35,4096² 具体锚点,同任务直接扫描对照 | 384.348 → 0.431167 ms;五轮中位数,预计算计入总时间,实际连接 3 对,12 个种子 | 全 lower35 生成。窗口从 (51069,419124) 开始,lower35=10565146255 |
|
||||||
|
| 坐标几何跨 256 平面复用 | 总耗时改善约 1.526× | 仍逐平面重建种子相关 profile |
|
||||||
|
| 同低 18 位的平面缓存与 bitset | 总耗时改善约 1.773× | 不重复的低位取值没有缓存收益 |
|
||||||
|
| 给定 lower35 的具体坐标类展开 | 全范围约 136.426 ms,对相同粗类配对过滤的逐坐标参考改善约 2.270× | 全种子搜索;高层类拆分没有新增剪枝 |
|
||||||
|
|
||||||
|
已知命中样例为世界种子 `61692151016558`、锚点 `(51069,419124)`。
|
||||||
|
单平面收益只能说明过滤连接原语有效,不能用潜在坐标笛卡尔积大小当实际访问次数,或据此推算完整目录生成时间。
|
||||||
|
|
||||||
|
## 核心瓶颈:低 35 位生成
|
||||||
|
|
||||||
|
首次输出 `next(31)` 丢弃内部状态低 17 位,输出奇偶由状态 bit17 决定,所以奇偶过滤只依赖输入低 18 位。固定坐标后,更高位无法改变这个条件,逐位扩展到 35 位不会自动获得新剪枝。
|
||||||
|
|
||||||
|
已知 5×5 样例有 84 个低 18 位根;补上中间 17 位就是
|
||||||
|
`84 × 2^17 = 11,010,048` 个 lower35。优化最后的 upper13 求解、缓存和稀疏连接,不能消除前面的生成成本。
|
||||||
|
|
||||||
|
低于 18 位时,用不同输出位的奇偶代替实际 bit17 是错误的必要条件:已知有效样例在深度 8、10、12、16、17 都不能让全部 25 格满足那个替代条件,会被误删。
|
||||||
|
|
||||||
|
## 补救尝试:输出差后缀索引
|
||||||
|
|
||||||
|
首次输出为 `q_i`。高 13 位给所有格子施加共同的模 `2^31` 平移,因此输出差
|
||||||
|
`delta_i=(q_i-q_0) mod 2^31` 与高 13 位无关。两个输出均为 10 的倍数时,必须有
|
||||||
|
`delta_i mod 10 ∈ {0,8}`,其中 8 来自回绕。
|
||||||
|
|
||||||
|
将输入拆为低 24 位、中间 11 位、高 13 位。对每格的两个加法进位情况,各预计算 2,048 个后缀差值,按模 5 余数和回绕阈值索引。查询产生 2,048 位集合,多格取交,仅为交集后缀构造 lower35,随后解析求解全部 25 格。
|
||||||
|
|
||||||
|
固定坐标样例:包含建表的单线程中位数由 46.914 ms 降到 6.813 ms(6.89×),基线也使用相同输出差过滤。进入精确求解的 lower35 从 11,010,048 个降到 990 个,最终两个兼容平面、24 个种子一致。其他三个有根样例改善 2.52~5.12×;14 格索引数据约 2.08 MB。
|
||||||
|
|
||||||
|
但索引依赖相对坐标项。对世界上每个坐标对分别建表,会重新引入坐标笛卡尔积。
|
||||||
|
|
||||||
|
## 联合索引:仍被过早配对卡住
|
||||||
|
|
||||||
|
后续版本共享 X/Z 轴索引,只对共同后缀直接检查内部格子,以避免为每个配对建立内部索引。它搜索有限窗口中的全部首次输出候选种子空间。
|
||||||
|
|
||||||
|
| 坐标窗口 | 标量中间位枚举 ms | 每对单独索引 ms | 共享轴索引 ms |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| 1×1 | 47.942 | 7.398 | 7.333 |
|
||||||
|
| 4×4 | 351.076 | 64.923 | 40.264 |
|
||||||
|
| 8×8 | 773.763 | 163.317 | 89.139 |
|
||||||
|
| 16×16 | 919.179 | 223.608 | 116.371 |
|
||||||
|
|
||||||
|
窗口均从已知锚点开始,三轮中位数,共同几何与 roots 构建计入各路径总时间。16×16 共享索引约 13.46 MB,逐配对索引约 135.42 MB。这组有限样例不是随机坐标的平均性能。
|
||||||
|
|
||||||
|
决定停止的证据是粗配对密度审计:抽取 256 个锚点首抽为偶数的真实低 18 位根,统计轴过滤后的配对数。
|
||||||
|
|
||||||
|
| 坐标窗口 | 每根粗对数中位数 | 最小值 | 最大值 |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| 64×64 | 15 | 0 | 77 |
|
||||||
|
| 1024×1024 | 4,289 | 2,542 | 7,110 |
|
||||||
|
| 65536×65536 | 17,893,124 | 11,634,172 | 23,028,228 |
|
||||||
|
|
||||||
|
内部约束生效前,一个根已经留下上千万粗对。后续淘汰再强,也已经支付访问配对的成本。审计以两侧存活数乘积精确计数,没有运行大窗口完整搜索;不是完整耗时,也不是所有根的严格总数。缩小表、优化末尾检查或搬到 GPU,均未解决当前流程过早配对的问题。
|
||||||
|
|
||||||
|
## 其他否决的尝试
|
||||||
|
|
||||||
|
- 精确后缀真值表/BDD:先枚举全部 `2^17` 后缀才能建表,建表完成了原本要避免的工作。
|
||||||
|
- 算术前向状态缓存:未来行为相同的节点仍具有不同进位与余数状态,实测没有获得有效状态合并。
|
||||||
|
- 通用 SMT 后缀存在性判定:一个可满足前缀约 4.12 秒,对标量约 76 微秒;16 个无解前缀均触及 100 ms 超时,不适合在线生成。
|
||||||
|
- 配对后根集合去重:2,016 个非空对产生 2,013 个不同根集合,无法压缩主要工作。
|
||||||
|
- 高位倒排桶:首次平面发出 131,194 条记录,只得到 766 个不同对;比直接连接已存活的 2,176 对更昂贵。
|
||||||
|
- 显式联合高位分支:连接慢约 5.49×,跨叶重复访问约 7.46×;坐标树相对平坦 bitset 收益很小。
|
||||||
|
- 始终开启低位缓存:1,024 个不同低位取值零命中,profile 构建约 7,546.50 ms,对无缓存约 6,308.21 ms。
|
||||||
|
- 尾部微调:约 235 亿个 lower35 中仅 133,815 个通过十格条件,优化这些稀疏尾项难以改变总成本。
|
||||||
|
|
||||||
|
## 正确性与历史验证
|
||||||
|
|
||||||
|
固定坐标索引通过 26 轮完整结果对照及 4,194,304 组输出差恒等式检查;联合索引通过 21 轮四路径完整列表比较,并用不分轴的直接算术核对 roots 覆盖。最终命中由独立 Python Java Random 检查全部 25 格。ASan/UBSan 小范围检查通过,受环境 ptrace 限制关闭 LeakSanitizer,不声称验证内存泄漏。
|
||||||
|
|
||||||
|
所有这些生成器仍沿用首次输出候选模型,未完整覆盖 Java `nextInt(10)` 拒绝后重抽产生的解。最终准确验证能排除误报,不能恢复生成阶段漏掉的解。历史结果一致不等于严格穷尽 Java 种子。
|
||||||
|
|
||||||
|
## 外部线索与停止条件
|
||||||
|
|
||||||
|
公开的 [Badel2/slime_seed_finder](https://github.com/Badel2/slime_seed_finder/blob/master/src/slime.rs) 使用低 18 位过滤后枚举剩余位;[pruby/slime-seed](https://github.com/pruby/slime-seed/blob/master/slime_algo.c) 有从满足条件的输出逆推种子的候选生成思路;[UltraSlimeChunkFinder](https://github.com/alchemyyy/UltraSlimeChunkFinder/blob/main/slime.cu) 有逐坐标 CUDA 分位搜索,但其步长完整性没有在本项目验证。这些源码未提供可直接补上本路线缺口的实现。
|
||||||
|
|
||||||
|
[WKD SlimeBase](https://wkd.gg/slimebase/) 声称完成世界边界内全部 5×5 目录(454 个位置、90,331 个 48 位种子);[作者说明](https://www.reddit.com/r/technicalminecraft/comments/1wi8l8v/i_found_every_possible_5x5_slime_chunk_cluster_in/) 提到 CUDA 和 RTX 4080 Super。本次未找到搜索器源码,也未独立验证目录完整性,不能据此认定作者使用了本路线。
|
||||||
|
|
||||||
|
若未来重启研究,需要提供在坐标配对物化之前应用内部约束的候选构造,并测量包含建表、低位生成、连接和完整搜索的总成本。只改善给定平面或小窗口的速度,不足以恢复这条路线。
|
||||||
|
|
||||||
|
## 清理范围
|
||||||
|
|
||||||
|
移除了轴 profile、稀疏连接、坐标类、联合 census 与 CUDA 诊断、中间位索引的实现和二进制;移除了对应测试、测速脚本、JSON 结果和包含代码快照的旧文档。`show/` 的 09/10 教学版本及独立筛轴视频章节同步移除,含该章节的旧视频和字幕作废并删除。构建与文档入口改为保留的固定种子/固定坐标任务。
|
||||||
|
|
||||||
|
坐标项的 `F(x)+G(z)` 恒等式与 tiled outer-sum 复用仍用于固定种子扫描,属于有效的算术复用,不依赖被停止的独立筛轴候选连接路线。
|
||||||
186
research/benchmark_suite.py
Normal file
186
research/benchmark_suite.py
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run reproducible multi-position CPU/CUDA benchmarks."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import statistics
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
WORLD_MIN = -1_875_000
|
||||||
|
WORLD_MAX_TOP_LEFT_5 = 1_874_996
|
||||||
|
|
||||||
|
|
||||||
|
def positions(count: int) -> list[tuple[int, int]]:
|
||||||
|
known = (51_069, 419_124)
|
||||||
|
result = [known]
|
||||||
|
hard_target = max(1, count // 2)
|
||||||
|
offsets = sorted(
|
||||||
|
((dx, dz) for dz in range(-8, 9) for dx in range(-8, 9) if dx or dz),
|
||||||
|
key=lambda item: (abs(item[0]) + abs(item[1]), item[1], item[0]),
|
||||||
|
)
|
||||||
|
for dx, dz in offsets:
|
||||||
|
if len(result) >= hard_target:
|
||||||
|
break
|
||||||
|
result.append((known[0] + dx, known[1] + dz))
|
||||||
|
|
||||||
|
fixed = [
|
||||||
|
(0, 0),
|
||||||
|
(WORLD_MIN, WORLD_MIN),
|
||||||
|
(WORLD_MIN, WORLD_MAX_TOP_LEFT_5),
|
||||||
|
(WORLD_MAX_TOP_LEFT_5, WORLD_MIN),
|
||||||
|
(WORLD_MAX_TOP_LEFT_5, WORLD_MAX_TOP_LEFT_5),
|
||||||
|
(-1, -1),
|
||||||
|
(1, 1),
|
||||||
|
(-1_000_000, 1_000_000),
|
||||||
|
(1_000_000, -1_000_000),
|
||||||
|
]
|
||||||
|
for position in fixed:
|
||||||
|
if len(result) >= count:
|
||||||
|
break
|
||||||
|
if position not in result:
|
||||||
|
result.append(position)
|
||||||
|
rng = random.Random(20_261_004)
|
||||||
|
while len(result) < count:
|
||||||
|
result.append(
|
||||||
|
(
|
||||||
|
rng.randint(WORLD_MIN, WORLD_MAX_TOP_LEFT_5),
|
||||||
|
rng.randint(WORLD_MIN, WORLD_MAX_TOP_LEFT_5),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return result[:count]
|
||||||
|
|
||||||
|
|
||||||
|
def run(command: list[str]) -> str:
|
||||||
|
completed = subprocess.run(command, text=True, capture_output=True, check=False)
|
||||||
|
if completed.returncode != 0:
|
||||||
|
sys.stderr.write(completed.stdout)
|
||||||
|
sys.stderr.write(completed.stderr)
|
||||||
|
raise RuntimeError(f"command failed ({completed.returncode}): {' '.join(command)}")
|
||||||
|
return completed.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def fields(output: str) -> dict[str, float]:
|
||||||
|
result: dict[str, float] = {}
|
||||||
|
for key, value in re.findall(r"([a-zA-Z0-9_]+)=([0-9.eE+-]+)", output.splitlines()[0]):
|
||||||
|
result[key] = float(value)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def percentile(values: list[float], fraction: float) -> float:
|
||||||
|
ordered = sorted(values)
|
||||||
|
index = math.ceil(fraction * len(ordered)) - 1
|
||||||
|
return ordered[max(0, min(index, len(ordered) - 1))]
|
||||||
|
|
||||||
|
|
||||||
|
def stats(values: list[float]) -> dict[str, float]:
|
||||||
|
return {
|
||||||
|
"min": min(values),
|
||||||
|
"median": statistics.median(values),
|
||||||
|
"mean": statistics.fmean(values),
|
||||||
|
"p95": percentile(values, 0.95),
|
||||||
|
"max": max(values),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--samples", type=int, default=64)
|
||||||
|
parser.add_argument("--cpu-threads", type=int, default=16)
|
||||||
|
parser.add_argument("--cpu-old-count", type=int, default=1 << 28)
|
||||||
|
parser.add_argument("--cuda-old-count", type=int, default=1 << 34)
|
||||||
|
parser.add_argument("--cuda-threads", type=int, default=256)
|
||||||
|
parser.add_argument("--skip-cpu", action="store_true")
|
||||||
|
parser.add_argument("--skip-cuda", action="store_true")
|
||||||
|
parser.add_argument("--output", type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.samples <= 0:
|
||||||
|
parser.error("samples must be positive")
|
||||||
|
|
||||||
|
rows: list[dict[str, object]] = []
|
||||||
|
sample_positions = positions(args.samples)
|
||||||
|
for index, (x, z) in enumerate(sample_positions):
|
||||||
|
row: dict[str, object] = {"index": index, "x": x, "z": z}
|
||||||
|
common = ["--x", str(x), "--z", str(z), "-n", "5"]
|
||||||
|
|
||||||
|
if not args.skip_cpu:
|
||||||
|
new_cpu = fields(run([
|
||||||
|
str(HERE / "slime_pattern_cpp"), "search", *common,
|
||||||
|
"-p", str(args.cpu_threads), "--max-results", "0",
|
||||||
|
]))
|
||||||
|
old_cpu = fields(run([
|
||||||
|
str(HERE / "slime_bruteforce_cpp_bench"), *common,
|
||||||
|
"--count", str(args.cpu_old_count), "-p", str(args.cpu_threads),
|
||||||
|
]))
|
||||||
|
row["new_cpu_seconds"] = new_cpu["seconds"]
|
||||||
|
row["old_cpu_projected_seconds"] = old_cpu["projected_full48_seconds"]
|
||||||
|
row["cpu_speedup"] = old_cpu["projected_full48_seconds"] / new_cpu["seconds"]
|
||||||
|
row["lower18_roots"] = int(new_cpu["lower18_roots"])
|
||||||
|
row["lower35_checked"] = int(new_cpu["lower35_checked"])
|
||||||
|
row["exact_matches_cpu"] = int(new_cpu["exact_matches"])
|
||||||
|
row["old_cpu_measured_seeds"] = args.cpu_old_count
|
||||||
|
|
||||||
|
if not args.skip_cuda:
|
||||||
|
new_cuda = fields(run([
|
||||||
|
str(HERE / "slime_pattern_cuda"), *common,
|
||||||
|
"--threads", str(args.cuda_threads), "--max-results", "0",
|
||||||
|
]))
|
||||||
|
old_cuda = fields(run([
|
||||||
|
str(HERE / "slime_bruteforce_cuda_bench"), *common,
|
||||||
|
"--count", str(args.cuda_old_count),
|
||||||
|
]))
|
||||||
|
new_cuda_seconds = new_cuda["milliseconds"] / 1000.0
|
||||||
|
row["new_cuda_seconds"] = new_cuda_seconds
|
||||||
|
row["old_cuda_projected_seconds"] = old_cuda["projected_full48_seconds"]
|
||||||
|
row["cuda_speedup"] = (
|
||||||
|
old_cuda["projected_full48_seconds"] / new_cuda_seconds
|
||||||
|
if new_cuda_seconds > 0.0 else None
|
||||||
|
)
|
||||||
|
row["exact_matches_cuda"] = int(new_cuda["exact_matches"])
|
||||||
|
row["old_cuda_measured_seeds"] = args.cuda_old_count
|
||||||
|
|
||||||
|
rows.append(row)
|
||||||
|
print(json.dumps(row, separators=(",", ":")), flush=True)
|
||||||
|
|
||||||
|
summary: dict[str, object] = {
|
||||||
|
"samples": args.samples,
|
||||||
|
"positions": sample_positions,
|
||||||
|
"rows": rows,
|
||||||
|
}
|
||||||
|
numeric_keys = [
|
||||||
|
"lower18_roots",
|
||||||
|
"lower35_checked",
|
||||||
|
"new_cpu_seconds",
|
||||||
|
"old_cpu_projected_seconds",
|
||||||
|
"cpu_speedup",
|
||||||
|
"new_cuda_seconds",
|
||||||
|
"old_cuda_projected_seconds",
|
||||||
|
"cuda_speedup",
|
||||||
|
]
|
||||||
|
summary["statistics"] = {
|
||||||
|
key: stats([float(row[key]) for row in rows if row.get(key) is not None])
|
||||||
|
for key in numeric_keys
|
||||||
|
if key in rows[0] and any(row.get(key) is not None for row in rows)
|
||||||
|
}
|
||||||
|
summary["aggregate"] = {
|
||||||
|
"old_cpu_measured_seeds": sum(int(row.get("old_cpu_measured_seeds", 0)) for row in rows),
|
||||||
|
"old_cuda_measured_seeds": sum(int(row.get("old_cuda_measured_seeds", 0)) for row in rows),
|
||||||
|
"lower35_checked": sum(int(row.get("lower35_checked", 0)) for row in rows),
|
||||||
|
"exact_matches_cpu": sum(int(row.get("exact_matches_cpu", 0)) for row in rows),
|
||||||
|
"exact_matches_cuda": sum(int(row.get("exact_matches_cuda", 0)) for row in rows),
|
||||||
|
}
|
||||||
|
print("SUMMARY " + json.dumps(summary, separators=(",", ":")), flush=True)
|
||||||
|
if args.output is not None:
|
||||||
|
args.output.write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
11
research/experiments/README.md
Normal file
11
research/experiments/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# 实验导航
|
||||||
|
|
||||||
|
| 目录 | 研究问题 | 结果入口 |
|
||||||
|
|---|---|---|
|
||||||
|
| `full_search_feasibility/` | 输出差过滤、区间求交、低位类复用及严格拒绝补扫的成本 | [报告](full_search_feasibility/REPORT.md) |
|
||||||
|
| `normal_search/` | 普通分支的 16 候选成组筛选 | [报告](normal_search/REPORT.md) |
|
||||||
|
| `normal_search/` | 把类复用和仅展开有根位置接入批次调度 | [流程报告](normal_search/PIPELINE_REPORT.md) |
|
||||||
|
|
||||||
|
这里的 `positions/`、`pipeline_positions/`、`results/`、`pipeline_results/` 是报告和复现脚本使用的数据,作为研究记录保留。日志和空 stderr 文件忽略;二进制一般输出到 `/tmp`。
|
||||||
|
|
||||||
|
日常比较本机与服务器的性能请用 [当前 CLI](../../bench/README.md)。实验程序保留用于受控对照,不承担正式 CLI 的职责。普通分支与补扫的覆盖范围不可混用。
|
||||||
135
research/experiments/full_search_feasibility/REPORT.md
Normal file
135
research/experiments/full_search_feasibility/REPORT.md
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
# 实验结论:定点求解显著提速,全世界目录仍不适合启动
|
||||||
|
|
||||||
|
日期:2026-10-11,Asia/Hong_Kong。硬件:RTX 3060 Laptop、Ryzen 7 5800H,WSL,CUDA 12.0。
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
1. **保留输出差过滤+逐格区间求交。** GPU 搜索 kernel 在三组同任务对照中快 197~292 倍,完整输出一致。
|
||||||
|
2. **保留低位坐标类复用。** 52,213 个实际位置的完整低位根列表与 32 个代表类一致;它减少根生成,不能消除后续中间位枚举。
|
||||||
|
3. **否决低位位表查找替换。** 此 GPU 上查表比直接整数计算多耗时约 36%。
|
||||||
|
4. **保留按首个奇数格逆推拒绝尾部。** 相比逐格枚举全部尾部,128 坐标批次快约 24 倍,并覆盖多次拒绝和多个格子首次拒绝的情况。
|
||||||
|
5. **当前路线仍不足以生成全世界全部种子的目录。** 随机批次吞吐外推:首次输出分支约 0.9 年;包含拒绝重抽的完整分支约 16 年。这里已乐观地摊薄进程启动、复用低位根生成,不是实际跑完全世界,也不是所有可能算法的下界。
|
||||||
|
|
||||||
|
代码是独立实验,没有替换原来的正式入口。
|
||||||
|
|
||||||
|
## GPU 求解器对照
|
||||||
|
|
||||||
|
全部模式处理相同的 lower35 候选,完整预热后轮换顺序测量五轮,核对排序后的完整 `(world_seed,x,z)` 列表。
|
||||||
|
|
||||||
|
| 负载 | lower35 数 | 原排序事件求解器 | 仅区间求交 | 输出差+原求解器 | 输出差+区间求交 | 最佳相对原版 |
|
||||||
|
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||||
|
| 已知位置 `(51069,419124)` | 11,010,048 | 63.750 ms | 0.763 ms | 0.390 ms | **0.324 ms** | **197.0×** |
|
||||||
|
| 21 个压力/边界坐标 | 62,128,128 | 363.876 ms | 4.674 ms | 1.757 ms | **1.245 ms** | **292.2×** |
|
||||||
|
| 4096 个均匀随机坐标 | 387,973,120 | 2318.625 ms | 29.380 ms | 12.297 ms | **8.157 ms** | **284.2×** |
|
||||||
|
| 65536 个均匀随机坐标 | 5,944,901,632 | 未重测 | 407.659 ms | 175.974 ms | **131.670 ms** | — |
|
||||||
|
|
||||||
|
以上均为 CUDA event kernel 中位数,不包括低位根生成、CUDA 初始化、分配和传输。21 坐标组包含已知命中附近位置、原点、世界边界和 int32 边界;它不代表随机平均坐标。
|
||||||
|
|
||||||
|
已知位置和压力组均得到相同的 24 个种子。两个随机组没有命中。随机无命中不单独构成正确性证据,另有下面的独立校验。
|
||||||
|
|
||||||
|
主要收益来自更早排除不可能共同满足模 10 条件的候选:
|
||||||
|
|
||||||
|
```
|
||||||
|
delta_i = (q_i(0) - q_0(0)) mod 2^31
|
||||||
|
必要条件:delta_i mod 10 属于 {0,8}
|
||||||
|
```
|
||||||
|
|
||||||
|
原因是改变高 13 位,对所有输出施加相同的模 `2^31` 平移;两个最终输出都是 10 的倍数时,差模 `2^31` 只能具有上述余数。多数候选在几个格子后就失败,不再支付 25 格的事件排序成本。差过滤通过后,最多两个“区间+余数”做逐格求交,再完整 Java 验证。
|
||||||
|
|
||||||
|
## 根分布与全局工作量
|
||||||
|
|
||||||
|
确定性均匀随机抽样 65536 个锚点,两轴范围 `[-1875000,1874999]`:
|
||||||
|
|
||||||
|
| 指标 | 结果 |
|
||||||
|
| --- | ---: |
|
||||||
|
| 首次输出低位无根比例 | 96.9162% |
|
||||||
|
| 低 18 位根总数 | 45,356 |
|
||||||
|
| 每坐标平均根数 | 0.69208 |
|
||||||
|
| 平均数的正态近似 95% 区间 | 0.61054~0.77361 |
|
||||||
|
| 最大根数 | 1015 |
|
||||||
|
|
||||||
|
分布有较长尾部。置信区间只说明该抽样模型中的根数均值不确定性,不包含 GPU 频率、长期运行、分组调度及模型覆盖的误差。
|
||||||
|
|
||||||
|
按样本均值,世界的 `14,062,500,000,000` 个锚点仍约有:
|
||||||
|
|
||||||
|
```
|
||||||
|
14,062,500,000,000 × 0.69207763671875 × 131072
|
||||||
|
= 1.2756375 × 10^18 个 lower35 候选
|
||||||
|
```
|
||||||
|
|
||||||
|
新 kernel 在 65536 坐标批次中约处理 451.5 亿个 lower35/秒。把实测批次吞吐线性扩展到全部锚点,首次输出分支约 **0.895 年**。这已不适合用早先的“已知命中坐标耗时 × 所有坐标”去估计成几万年,但距离几周完成仍有明显差距。
|
||||||
|
|
||||||
|
## 坐标类复用和位表查找
|
||||||
|
|
||||||
|
归一化有效种子 `s=world_seed+T(x,z)` 后,图案相对坐标项的低 18 位在 X 上以 `65536`、Z 上以 `131072` 为周期。
|
||||||
|
|
||||||
|
选取 32 个有根的类,并展开成世界内全部 52,213 个实际锚点。程序逐一比较每个同类位置的**完整归一化低位根列表**,全部一致。
|
||||||
|
|
||||||
|
| 低位筛选负载 | 直接整数计算 | 32 KiB 位表查找 |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| 65536 随机坐标 | **280.639 ms** | 380.533 ms |
|
||||||
|
| 32 个类代表 | **0.138 ms** | 0.209 ms |
|
||||||
|
| 52,213 个实际位置重复计算 | **293.866 ms** | 409.952 ms |
|
||||||
|
|
||||||
|
因此查表方案不保留。类复用在算法上成立,表中只比较筛选 kernel,未把归类、展开和传输成本当作零成本的实测完整方案。全世界最多 `2^33` 个类,按大批次根筛选吞吐粗略外推,代表类筛选约 10.2 小时;更高位求解仍须处理具体坐标,不能再乘一个“1637 倍”全流程加速。
|
||||||
|
|
||||||
|
首次输出无根并不排除拒绝重抽解,所以不能据此跳过完整语义的异常分支。
|
||||||
|
|
||||||
|
## 完整覆盖拒绝重抽
|
||||||
|
|
||||||
|
朴素补扫对 25 格分别枚举 `8 × 2^17` 个首次拒绝状态,共 26,214,400 个候选/坐标。
|
||||||
|
|
||||||
|
本轮新增的规范化补扫只枚举一次有效种子的低 18 位 `p`:
|
||||||
|
|
||||||
|
1. 从 p 算出每格首次输出的奇偶。
|
||||||
|
2. 若存在奇数格,选第一个。任何真实解必须在这一格首次拒绝,否则不可能返回 0。
|
||||||
|
3. 该格状态的低 18 位已确定,在拒绝区间中恰好有四个完整推进后状态与它一致;逆推这四个完整种子,执行全部格子的精确 Java 检查。
|
||||||
|
4. 若全部格子都是偶数,则为每一格补扫四个拒绝状态。
|
||||||
|
|
||||||
|
若一个坐标有 R 个全偶低位根,最多生成 `2^20 + 96R` 个补扫候选。无需假设“多个格子同时拒绝不可能”;选择首个奇数格的必要性对这种情况也成立。所有首次输出都被接受的真实解由普通分支覆盖,任何首次拒绝的真实解由补扫覆盖。最终并集去重。
|
||||||
|
|
||||||
|
| 对照任务 | 逐格朴素补扫 | 规范化补扫 | 去重命中数 |
|
||||||
|
| --- | ---: | ---: | ---: |
|
||||||
|
| 已知坐标的单格约束 | 0.032 ms | 0.026 ms | 104,848 |
|
||||||
|
| 同坐标前两格约束 | 0.065 ms | 0.053 ms | 20,888 |
|
||||||
|
| 同坐标前五格约束 | 0.139 ms | 0.084 ms | 109 |
|
||||||
|
| 128 坐标的完整 5×5 | 93.597 ms | **3.908 ms** | 0 |
|
||||||
|
|
||||||
|
两种补扫逐项比较完整去重列表,单格、多格的非空结果也一致。
|
||||||
|
|
||||||
|
## 同批完整流程:最终成本判断
|
||||||
|
|
||||||
|
在同一个 65536 随机坐标批次中,普通分支和规范化拒绝补扫顺序执行,合并、排序、去重;完整预热后测五轮:
|
||||||
|
|
||||||
|
| 区域 | 中位数 |
|
||||||
|
| --- | ---: |
|
||||||
|
| 一次低位根准备 kernel | 296.573 ms(单次) |
|
||||||
|
| 普通搜索 kernel | 129.157 ms |
|
||||||
|
| 完整拒绝补扫 kernel | 2283.082 ms |
|
||||||
|
| 两个 kernel 的每轮耗时之和 | **2413.373 ms** |
|
||||||
|
| 同步、回传、合并在内的 host scan | **2356.676 ms** |
|
||||||
|
|
||||||
|
另在 21 坐标压力组完成同样的完整流程,全部 24 个已知结果保留,无额外拒绝解;完整结果已与此前普通分支核对。
|
||||||
|
|
||||||
|
本环境观察到 CUDA event 累计时间比 host wall time 高约 2.4%;两种时钟口径分别保留,不用它们混合计算局部加速比。这一差异的原因未单独确定,长期外推只按数量级解释。
|
||||||
|
|
||||||
|
按同批完整测量,外推全世界为 **16.0~16.4 年**。这里不重复支付进程启动,低位筛选可按坐标类摊薄;尚未计入全世界调度、建表、长期频率波动和输出持久化等工程成本。不能宣称已实现完整世界扫描。
|
||||||
|
|
||||||
|
补扫约占计算时间的 95%。**即使完全删掉这个正确性成本,普通分支仍约需一年。** 因此结论不依赖于罕见分支是否必须支持:这轮优化值得用于定点反查和有限批次,但仍不应启动全世界目录生成。
|
||||||
|
|
||||||
|
## 正确性检查与复现
|
||||||
|
|
||||||
|
- CPU 独立直接枚举 4096 个高位平面,共 33,554,432 个完整种子,覆盖 1~25 格约束、随机坐标和已知命中平面。原求解器、区间求解器和输出差过滤版本的完整列表一致。
|
||||||
|
- 用单独的 Java 算术实现核对全部 1,048,576 个单格拒绝状态及逆推;其中存在 52,422 个首次输出为奇数、重抽后成功的例子,防止测试只覆盖空结果。
|
||||||
|
- CPU 核对 250,000 个格子的坐标周期;GPU 每批抽取最多 64 个坐标完整核对低位根表,并对所有重复坐标类比较完整根列表。
|
||||||
|
- GPU 四种求解器、两种根筛选、两种拒绝补扫分别完整核对结果,容量溢出会失败。
|
||||||
|
- GPU 与桌面共享,五轮原始数据、温度、驱动、源码哈希及每项命令保存在结果文件。早期对照和后补完整流程的源码哈希分别记录,计算 kernel 的算法一致。
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 research/experiments/full_search_feasibility/run.py
|
||||||
|
# GPU 需可访问;无 GPU 时可单独编译运行 cpu_verify.cpp。
|
||||||
|
python3 research/experiments/full_search_feasibility/run.py --analyse-only
|
||||||
|
```
|
||||||
|
|
||||||
|
核心代码:[interval_solver.hpp](interval_solver.hpp)、[cuda_bench.cu](cuda_bench.cu)。
|
||||||
|
汇总:[results/summary.json](results/summary.json)。完整实验数据:[results/all.json](results/all.json)。
|
||||||
73
research/experiments/full_search_feasibility/cpu_verify.cpp
Normal file
73
research/experiments/full_search_feasibility/cpu_verify.cpp
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#include "interval_solver.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr=slime_research;
|
||||||
|
namespace fs=full_search_experiment;
|
||||||
|
struct Emit {std::vector<uint64_t>& result;void operator()(uint64_t s){result.push_back(s);}};
|
||||||
|
void require(bool b,const char*message){if(!b)throw std::runtime_error(message);}
|
||||||
|
|
||||||
|
// Separate Java arithmetic oracle. Every conversion is explicit to avoid signed overflow.
|
||||||
|
uint64_t term(int32_t x,int32_t z){
|
||||||
|
auto sx=[](uint32_t n){return uint64_t(int64_t(int32_t(n)));};
|
||||||
|
uint32_t a=uint32_t(x),b=uint32_t(z);
|
||||||
|
return sx(a*a*4987142U)+sx(a*5947611U)+sx(b*b)*4392871ULL+sx(b*389711U);
|
||||||
|
}
|
||||||
|
bool exact(uint64_t seed,uint64_t coordinate){
|
||||||
|
uint64_t s=((seed+coordinate)^987234911ULL^0x5DEECE66DULL)&((1ULL<<48)-1);
|
||||||
|
for(;;){s=(s*0x5DEECE66DULL+11)&((1ULL<<48)-1);uint32_t q=s>>17;
|
||||||
|
if(q<2147483640U)return q%10==0;}
|
||||||
|
}
|
||||||
|
int main(){
|
||||||
|
std::mt19937_64 rng(2026101102);
|
||||||
|
uint64_t checked=0;
|
||||||
|
for(int test=0;test<4096;++test){
|
||||||
|
int32_t x=int32_t(rng()%3750000)-1875000,z=int32_t(rng()%3750000)-1875000;
|
||||||
|
if(test<24){x=51069;z=419124;}
|
||||||
|
std::array<sr::CellTerm,25> cells;
|
||||||
|
for(int j=0;j<5;j++)for(int i=0;i<5;i++)cells[j*5+i]={term(x+i,z+j),sr::kSlime};
|
||||||
|
int count=test<24?25:1+test%25;
|
||||||
|
uint64_t low=rng()&((1ULL<<35)-1);
|
||||||
|
if(test<24)low=(test%2?15959335054446ULL:61692151016558ULL)&((1ULL<<35)-1);
|
||||||
|
std::vector<uint64_t> direct,old,interval,filtered;Emit a{old},b{interval},c{filtered};
|
||||||
|
sr::solveUpper13(low,cells.data(),count,a);fs::solveIntervals(low,cells.data(),count,b);
|
||||||
|
if(fs::differenceFilter(low,cells.data(),count))fs::solveIntervals(low,cells.data(),count,c);
|
||||||
|
for(uint64_t h=0;h<8192;++h){
|
||||||
|
uint64_t s=low+(h<<35);bool ok=true;
|
||||||
|
for(int i=0;i<count;++i){
|
||||||
|
uint64_t v=(((s+cells[i].term)^0x5E434E432ULL)*0x5DEECE66DULL+11)&((1ULL<<48)-1);
|
||||||
|
if((v>>17)%10!=0||!exact(s,cells[i].term)){ok=false;break;}
|
||||||
|
}
|
||||||
|
if(ok)direct.push_back(s);
|
||||||
|
}
|
||||||
|
std::sort(old.begin(),old.end());std::sort(interval.begin(),interval.end());std::sort(filtered.begin(),filtered.end());
|
||||||
|
require(old==direct&&interval==direct&&filtered==direct,"upper enumeration mismatch");checked+=8192;
|
||||||
|
}
|
||||||
|
uint64_t rejectedSlime=0,oddRejectedSlime=0;
|
||||||
|
for(uint64_t v=uint64_t(2147483640U)<<17;v<(1ULL<<48);++v){
|
||||||
|
const uint64_t t=term(-1875000,1874999);
|
||||||
|
const uint64_t u=((v-11)*0xDFE05BCB1365ULL)&((1ULL<<48)-1);
|
||||||
|
const uint64_t seed=((u^0x5E434E432ULL)-t)&((1ULL<<48)-1);
|
||||||
|
const uint64_t recovered=(((seed+t)^0x5E434E432ULL)*0x5DEECE66DULL+11)&((1ULL<<48)-1);
|
||||||
|
require(v==recovered,"tail inversion mismatch");
|
||||||
|
bool a=exact(seed,t),b=sr::exactIsSlimeFromTerm(seed,t);require(a==b,"Java oracle mismatch");
|
||||||
|
rejectedSlime+=a;oddRejectedSlime+=a&&((v>>17)&1);
|
||||||
|
}
|
||||||
|
require(oddRejectedSlime>0,"missing rejection regression example");
|
||||||
|
for(int n=0;n<10000;++n){
|
||||||
|
int32_t x=int32_t(rng()%3750000)-1875000,z=int32_t(rng()%3750000)-1875000;
|
||||||
|
for(int j=0;j<5;++j)for(int i=0;i<5;++i){
|
||||||
|
uint64_t d=(term(x+i,z+j)-term(x,z))&((1U<<18)-1);
|
||||||
|
require(d==((term(x+65536+i,z+j)-term(x+65536,z))&((1U<<18)-1)),"X periodicity mismatch");
|
||||||
|
require(d==((term(x+i,z+131072+j)-term(x,z+131072))&((1U<<18)-1)),"Z periodicity mismatch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout<<"{\"upper_planes\":4096,\"direct_seeds\":"<<checked
|
||||||
|
<<",\"tail_states\":1048576,\"tail_slime\":"<<rejectedSlime
|
||||||
|
<<",\"odd_tail_slime\":"<<oddRejectedSlime
|
||||||
|
<<",\"periodicity_cells\":250000,\"passed\":true}\n";
|
||||||
|
}
|
||||||
256
research/experiments/full_search_feasibility/cuda_bench.cu
Normal file
256
research/experiments/full_search_feasibility/cuda_bench.cu
Normal file
|
|
@ -0,0 +1,256 @@
|
||||||
|
#include "interval_solver.hpp"
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <numeric>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr = slime_research;
|
||||||
|
namespace fs = full_search_experiment;
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
#define CUDA(call) do { auto e = (call); if (e != cudaSuccess) throw std::runtime_error(std::string(#call)+": "+cudaGetErrorString(e)); } while (0)
|
||||||
|
struct Position { int32_t x, z; uint64_t anchor; sr::CellTerm cells[25]; };
|
||||||
|
struct Job { uint32_t position, root; };
|
||||||
|
struct Match { uint64_t position, seed; bool operator<(const Match& o)const{return std::tie(position,seed)<std::tie(o.position,o.seed);} bool operator==(const Match& o)const{return position==o.position&&seed==o.seed;} };
|
||||||
|
constexpr uint32_t MATCH_CAPACITY = 1U << 20;
|
||||||
|
constexpr uint32_t JOB_CAPACITY = 1U << 22;
|
||||||
|
|
||||||
|
Position makePosition(int32_t x,int32_t z) {
|
||||||
|
Position p{}; p.x=x;p.z=z;p.anchor=sr::coordinateTerm(x,z);
|
||||||
|
for(int j=0;j<5;++j)for(int i=0;i<5;++i)
|
||||||
|
p.cells[j*5+i]={(sr::coordinateTerm(x+i,z+j)-p.anchor)&sr::kMask48,sr::kSlime};
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int Mode>
|
||||||
|
__global__ void rootKernel(const Position* positions,uint32_t count,const uint32_t* good,
|
||||||
|
uint32_t* counts,Job* jobs,unsigned long long* total,bool collect) {
|
||||||
|
uint64_t idx=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;
|
||||||
|
if(idx >= (uint64_t(count)<<18)) return;
|
||||||
|
const uint32_t pos=uint32_t(idx>>18), root=uint32_t(idx)&((1U<<18)-1);
|
||||||
|
const Position& p=positions[pos];
|
||||||
|
for(int i=0;i<25;++i){
|
||||||
|
const uint32_t in=root+uint32_t(p.cells[i].term);
|
||||||
|
if constexpr(Mode==0){
|
||||||
|
if((((in^uint32_t(sr::kCombinedXor))*uint32_t(sr::kJavaMultiplier)+11U)>>17)&1U)return;
|
||||||
|
}else{
|
||||||
|
const uint32_t k=in&((1U<<18)-1);
|
||||||
|
if(((good[k>>5]>>(k&31))&1U)==0)return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atomicAdd(counts+pos,1U);
|
||||||
|
const auto at=atomicAdd(total,1ULL);
|
||||||
|
if(collect&&at<JOB_CAPACITY)jobs[at]={pos,root};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Emitter {
|
||||||
|
uint32_t position; uint64_t anchor; Match* out; unsigned long long* count;
|
||||||
|
__device__ void operator()(uint64_t s){
|
||||||
|
auto at=atomicAdd(count,1ULL);
|
||||||
|
if(at<MATCH_CAPACITY)out[at]={position,(s-anchor)&sr::kMask48};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template<int Mode>
|
||||||
|
__global__ void searchKernel(const Position* positions,const Job* jobs,uint32_t count,
|
||||||
|
Match* out,unsigned long long* total) {
|
||||||
|
uint64_t idx=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;
|
||||||
|
if(idx >= (uint64_t(count)<<17))return;
|
||||||
|
const Job job=jobs[idx>>17];
|
||||||
|
uint64_t low=job.root+((idx&((1U<<17)-1))<<18);
|
||||||
|
const Position& p=positions[job.position];
|
||||||
|
Emitter emit{job.position,p.anchor,out,total};
|
||||||
|
if constexpr(Mode==0)sr::solveUpper13(low,p.cells,25,emit);
|
||||||
|
else if constexpr(Mode==1)fs::solveIntervals(low,p.cells,25,emit);
|
||||||
|
else if constexpr(Mode==2){if(fs::differenceFilter(low,p.cells,25))sr::solveUpper13(low,p.cells,25,emit);}
|
||||||
|
else if constexpr(Mode==3){if(fs::differenceFilter(low,p.cells,25))fs::solveIntervals(low,p.cells,25,emit);}
|
||||||
|
}
|
||||||
|
__global__ void tailKernel(const Position* positions,uint32_t count,Match* out,unsigned long long* total,int cellCount){
|
||||||
|
const uint64_t idx=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;
|
||||||
|
if(idx >= uint64_t(count)*cellCount*(1U<<20))return;
|
||||||
|
const uint64_t cell=idx>>20;
|
||||||
|
const uint32_t pos=cell/cellCount,i=cell%cellCount;
|
||||||
|
const uint64_t v=(uint64_t(2147483640U)<<17)+(idx&((1U<<20)-1));
|
||||||
|
const Position& p=positions[pos];
|
||||||
|
const uint64_t u=((v-11)*0xDFE05BCB1365ULL)&sr::kMask48;
|
||||||
|
const uint64_t seed=((u^sr::kCombinedXor)-p.cells[i].term)&sr::kMask48;
|
||||||
|
if(sr::verifyPattern(seed,p.cells,cellCount)){
|
||||||
|
Emitter e{pos,p.anchor,out,total};e(seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an output is odd, a true solid-pattern seed must reject that output.
|
||||||
|
// Pick the first odd cell: four tail states correspond to each seed-low18.
|
||||||
|
// When every output is even, enumerate all cells' four tail states instead.
|
||||||
|
// This covers multiple-rejection paths too, without assuming they are impossible.
|
||||||
|
__global__ void canonicalTailKernel(const Position* positions,uint32_t count,Match* out,unsigned long long* total,int cellCount){
|
||||||
|
const uint64_t idx=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;
|
||||||
|
if(idx >= (uint64_t(count)<<18))return;
|
||||||
|
const uint32_t pos=idx>>18,root=idx&((1U<<18)-1);
|
||||||
|
const Position& p=positions[pos];
|
||||||
|
int firstOdd=-1;
|
||||||
|
for(int i=0;i<cellCount;++i)if((sr::nextStateFromTerm(root,p.cells[i].term)>>17)&1U){firstOdd=i;break;}
|
||||||
|
const int begin=firstOdd>=0?firstOdd:0,end=firstOdd>=0?firstOdd+1:cellCount;
|
||||||
|
for(int i=begin;i<end;++i){
|
||||||
|
const uint32_t vlow=uint32_t(sr::nextStateFromTerm(root,p.cells[i].term))&((1U<<18)-1);
|
||||||
|
for(uint32_t k=0;k<4;++k){
|
||||||
|
const uint64_t v=(uint64_t(2147483640U+(vlow>>17)+2*k)<<17)+(vlow&((1U<<17)-1));
|
||||||
|
const uint64_t u=((v-11)*0xDFE05BCB1365ULL)&sr::kMask48;
|
||||||
|
const uint64_t seed=((u^sr::kCombinedXor)-p.cells[i].term)&sr::kMask48;
|
||||||
|
if(sr::verifyPattern(seed,p.cells,cellCount)){Emitter e{pos,p.anchor,out,total};e(seed);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
Position* positions=nullptr; Job* jobs=nullptr; Match* matches=nullptr;
|
||||||
|
uint32_t* counts=nullptr;uint32_t* good=nullptr;unsigned long long* total=nullptr;
|
||||||
|
cudaEvent_t start,stop;
|
||||||
|
Device(const std::vector<Position>& p){
|
||||||
|
CUDA(cudaMalloc(&positions,p.size()*sizeof(Position)));CUDA(cudaMemcpy(positions,p.data(),p.size()*sizeof(Position),cudaMemcpyHostToDevice));
|
||||||
|
CUDA(cudaMalloc(&jobs,JOB_CAPACITY*sizeof(Job)));CUDA(cudaMalloc(&matches,MATCH_CAPACITY*sizeof(Match)));
|
||||||
|
CUDA(cudaMalloc(&counts,p.size()*sizeof(uint32_t)));CUDA(cudaMalloc(&total,sizeof(*total)));
|
||||||
|
std::array<uint32_t,8192> bits{};
|
||||||
|
for(uint32_t k=0;k<(1U<<18);++k)if(!((((k^uint32_t(sr::kCombinedXor))*uint32_t(sr::kJavaMultiplier)+11U)>>17)&1U))bits[k>>5]|=1U<<(k&31);
|
||||||
|
CUDA(cudaMalloc(&good,sizeof(bits)));CUDA(cudaMemcpy(good,bits.data(),sizeof(bits),cudaMemcpyHostToDevice));
|
||||||
|
CUDA(cudaEventCreate(&start));CUDA(cudaEventCreate(&stop));
|
||||||
|
}
|
||||||
|
~Device(){cudaFree(positions);cudaFree(jobs);cudaFree(matches);cudaFree(counts);cudaFree(total);cudaFree(good);cudaEventDestroy(start);cudaEventDestroy(stop);}
|
||||||
|
float finish(){CUDA(cudaGetLastError());CUDA(cudaEventRecord(stop));CUDA(cudaEventSynchronize(stop));float ms;CUDA(cudaEventElapsedTime(&ms,start,stop));return ms;}
|
||||||
|
uint64_t totalCount(){unsigned long long n;CUDA(cudaMemcpy(&n,total,sizeof(n),cudaMemcpyDeviceToHost));return n;}
|
||||||
|
float roots(uint32_t n,int mode,bool collect){
|
||||||
|
CUDA(cudaMemset(total,0,sizeof(*total)));CUDA(cudaMemset(counts,0,n*sizeof(uint32_t)));CUDA(cudaEventRecord(start));
|
||||||
|
unsigned blocks=unsigned(((uint64_t(n)<<18)+255)/256);
|
||||||
|
if(mode==0)rootKernel<0><<<blocks,256>>>(positions,n,good,counts,jobs,total,collect);
|
||||||
|
else rootKernel<1><<<blocks,256>>>(positions,n,good,counts,jobs,total,collect);
|
||||||
|
return finish();
|
||||||
|
}
|
||||||
|
float search(uint32_t n,int mode){
|
||||||
|
CUDA(cudaMemset(total,0,sizeof(*total)));CUDA(cudaEventRecord(start));
|
||||||
|
unsigned blocks=unsigned(((uint64_t(n)<<17)+255)/256);
|
||||||
|
if(blocks){
|
||||||
|
if(mode==0)searchKernel<0><<<blocks,256>>>(positions,jobs,n,matches,total);
|
||||||
|
if(mode==1)searchKernel<1><<<blocks,256>>>(positions,jobs,n,matches,total);
|
||||||
|
if(mode==2)searchKernel<2><<<blocks,256>>>(positions,jobs,n,matches,total);
|
||||||
|
if(mode==3)searchKernel<3><<<blocks,256>>>(positions,jobs,n,matches,total);
|
||||||
|
}
|
||||||
|
return finish();
|
||||||
|
}
|
||||||
|
std::vector<Match> results(){
|
||||||
|
uint64_t n=totalCount();if(n>MATCH_CAPACITY)throw std::runtime_error("match overflow");
|
||||||
|
std::vector<Match> r(n);CUDA(cudaMemcpy(r.data(),matches,n*sizeof(Match),cudaMemcpyDeviceToHost));std::sort(r.begin(),r.end());return r;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc,char**argv){
|
||||||
|
try{
|
||||||
|
if(argc<3)throw std::runtime_error("usage: cuda_bench positions.txt root|compare|fast|tail|complete [repeats=5] [tail-cells=25]");
|
||||||
|
std::ifstream in(argv[1]);if(!in)throw std::runtime_error("cannot open positions");
|
||||||
|
int32_t x,z;std::vector<Position> positions;while(in>>x>>z)positions.push_back(makePosition(x,z));
|
||||||
|
if(positions.empty()||positions.size()>65536)throw std::runtime_error("position count must be 1..65536");
|
||||||
|
std::string task=argv[2];int repeats=argc>3?std::stoi(argv[3]):5;
|
||||||
|
int tailCells=argc>4?std::stoi(argv[4]):25;
|
||||||
|
if(tailCells<1||tailCells>25)throw std::runtime_error("tail-cells must be 1..25");
|
||||||
|
auto processStart=Clock::now();Device d(positions);
|
||||||
|
std::cout<<std::setprecision(10);
|
||||||
|
cudaDeviceProp prop{};CUDA(cudaGetDeviceProperties(&prop,0));std::cout<<"{\"type\":\"device\",\"name\":\""<<prop.name<<"\",\"positions\":"<<positions.size()<<"}"<<std::endl;
|
||||||
|
std::vector<uint32_t> referenceCounts;
|
||||||
|
std::vector<Job> referenceJobs;
|
||||||
|
auto collectRoots=[&](int mode){
|
||||||
|
float ms=d.roots(positions.size(),mode,true);uint64_t total=d.totalCount();if(total>JOB_CAPACITY)throw std::runtime_error("job overflow");
|
||||||
|
std::vector<uint32_t> counts(positions.size());CUDA(cudaMemcpy(counts.data(),d.counts,counts.size()*sizeof(uint32_t),cudaMemcpyDeviceToHost));
|
||||||
|
std::vector<Job> jobs(total);CUDA(cudaMemcpy(jobs.data(),d.jobs,total*sizeof(Job),cudaMemcpyDeviceToHost));
|
||||||
|
std::sort(jobs.begin(),jobs.end(),[](Job a,Job b){return std::tie(a.position,a.root)<std::tie(b.position,b.root);});
|
||||||
|
if(referenceCounts.empty()){referenceCounts=counts;referenceJobs=jobs;}
|
||||||
|
else {
|
||||||
|
if(counts!=referenceCounts||jobs.size()!=referenceJobs.size())throw std::runtime_error("root mismatch");
|
||||||
|
for(size_t i=0;i<jobs.size();++i)if(jobs[i].position!=referenceJobs[i].position||jobs[i].root!=referenceJobs[i].root)throw std::runtime_error("root list mismatch");
|
||||||
|
}
|
||||||
|
return ms;
|
||||||
|
};
|
||||||
|
if(task=="root"){
|
||||||
|
collectRoots(0);collectRoots(1);
|
||||||
|
for(int r=0;r<repeats;++r)for(int k=0;k<2;++k){int mode=(r+k)%2;float ms=collectRoots(mode);std::cout<<"{\"type\":\"root_timing\",\"mode\":"<<mode<<",\"round\":"<<r<<",\"kernel_ms\":"<<ms<<"}"<<std::endl;}
|
||||||
|
}else if(task=="tail"){
|
||||||
|
std::vector<Match> expected;
|
||||||
|
for(int r=-1;r<repeats;++r)for(int k=0;k<2;++k){
|
||||||
|
const int mode=r<0?k:(r+k)%2;
|
||||||
|
CUDA(cudaMemset(d.total,0,sizeof(*d.total)));CUDA(cudaEventRecord(d.start));
|
||||||
|
const uint64_t work=mode==0?uint64_t(positions.size())*tailCells*(1U<<20):(uint64_t(positions.size())<<18);
|
||||||
|
if((work+255)/256>0x7fffffffULL)throw std::runtime_error("tail grid too large");
|
||||||
|
unsigned blocks=unsigned((work+255)/256);
|
||||||
|
if(mode==0)tailKernel<<<blocks,256>>>(d.positions,positions.size(),d.matches,d.total,tailCells);
|
||||||
|
else canonicalTailKernel<<<blocks,256>>>(d.positions,positions.size(),d.matches,d.total,tailCells);
|
||||||
|
float ms=d.finish();auto result=d.results();
|
||||||
|
result.erase(std::unique(result.begin(),result.end()),result.end());
|
||||||
|
if(r==-1&&mode==0)expected=result;else if(result!=expected)throw std::runtime_error("tail mismatch");
|
||||||
|
if(r>=0)std::cout<<"{\"type\":\"tail_timing\",\"mode\":"<<mode<<",\"cells\":"<<tailCells<<",\"round\":"<<r<<",\"kernel_ms\":"<<ms<<",\"matches\":"<<result.size()<<"}"<<std::endl;
|
||||||
|
}
|
||||||
|
std::cout<<"{\"type\":\"process\",\"ms\":"<<std::chrono::duration<double,std::milli>(Clock::now()-processStart).count()<<"}"<<std::endl;return 0;
|
||||||
|
}else if(task=="complete"){
|
||||||
|
const float rootMs=collectRoots(0);
|
||||||
|
CUDA(cudaMemcpy(d.jobs,referenceJobs.data(),referenceJobs.size()*sizeof(Job),cudaMemcpyHostToDevice));
|
||||||
|
std::vector<Match> expected;
|
||||||
|
for(int r=-1;r<repeats;++r){
|
||||||
|
auto wallStart=Clock::now();
|
||||||
|
const float firstMs=d.search(referenceJobs.size(),3);
|
||||||
|
auto result=d.results();
|
||||||
|
CUDA(cudaMemset(d.total,0,sizeof(*d.total)));CUDA(cudaEventRecord(d.start));
|
||||||
|
const unsigned blocks=unsigned(((uint64_t(positions.size())<<18)+255)/256);
|
||||||
|
canonicalTailKernel<<<blocks,256>>>(d.positions,positions.size(),d.matches,d.total,25);
|
||||||
|
const float tailMs=d.finish();auto tail=d.results();
|
||||||
|
result.insert(result.end(),tail.begin(),tail.end());std::sort(result.begin(),result.end());
|
||||||
|
result.erase(std::unique(result.begin(),result.end()),result.end());
|
||||||
|
if(r==-1)expected=result;else if(result!=expected)throw std::runtime_error("complete pipeline result mismatch");
|
||||||
|
if(r>=0)std::cout<<"{\"type\":\"complete_timing\",\"round\":"<<r<<",\"first_ms\":"<<firstMs
|
||||||
|
<<",\"tail_ms\":"<<tailMs<<",\"compute_ms\":"<<(firstMs+tailMs)
|
||||||
|
<<",\"scan_ms\":"<<std::chrono::duration<double,std::milli>(Clock::now()-wallStart).count()
|
||||||
|
<<",\"matches\":"<<result.size()<<"}"<<std::endl;
|
||||||
|
}
|
||||||
|
std::cout<<"{\"type\":\"complete_info\",\"root_ms\":"<<rootMs<<",\"jobs\":"<<referenceJobs.size()<<",\"matches\":[";
|
||||||
|
for(size_t i=0;i<expected.size();++i){if(i)std::cout<<",";const auto& p=positions[expected[i].position];std::cout<<"["<<expected[i].seed<<","<<p.x<<","<<p.z<<"]";}std::cout<<"]}"<<std::endl;
|
||||||
|
}else{
|
||||||
|
float rootMs=collectRoots(0);
|
||||||
|
CUDA(cudaMemcpy(d.jobs,referenceJobs.data(),referenceJobs.size()*sizeof(Job),cudaMemcpyHostToDevice));
|
||||||
|
std::vector<Match> expected;
|
||||||
|
const std::vector<int> modes=task=="compare"?std::vector<int>{0,1,2,3}:std::vector<int>{1,2,3};
|
||||||
|
bool first=true;
|
||||||
|
for(int m:modes){d.search(referenceJobs.size(),m);auto r=d.results();if(first){expected=r;first=false;}else if(r!=expected)throw std::runtime_error("warmup result mismatch");}
|
||||||
|
for(int r=0;r<repeats;++r)for(size_t k=0;k<modes.size();++k){
|
||||||
|
int mode=modes[(k+r)%modes.size()];auto wallStart=Clock::now();float ms=d.search(referenceJobs.size(),mode);auto result=d.results();
|
||||||
|
if(result!=expected)throw std::runtime_error("search result mismatch");
|
||||||
|
std::cout<<"{\"type\":\"search_timing\",\"mode\":"<<mode<<",\"round\":"<<r<<",\"kernel_ms\":"<<ms<<",\"scan_ms\":"<<std::chrono::duration<double,std::milli>(Clock::now()-wallStart).count()<<",\"matches\":"<<result.size()<<"}"<<std::endl;
|
||||||
|
}
|
||||||
|
std::cout<<"{\"type\":\"search_info\",\"root_ms\":"<<rootMs<<",\"jobs\":"<<referenceJobs.size()<<",\"lower35\":"<<(uint64_t(referenceJobs.size())<<17)<<",\"matches\":[";
|
||||||
|
for(size_t i=0;i<expected.size();++i){if(i)std::cout<<",";const auto& p=positions[expected[i].position];std::cout<<"["<<expected[i].seed<<","<<p.x<<","<<p.z<<"]";}std::cout<<"]}"<<std::endl;
|
||||||
|
}
|
||||||
|
// CPU checks of complete low18 root lists, including varied coordinates.
|
||||||
|
for(size_t n=0;n<std::min<size_t>(64,positions.size());++n){
|
||||||
|
size_t pos=n*positions.size()/std::min<size_t>(64,positions.size());std::vector<uint32_t> cpu,gpu;
|
||||||
|
for(uint32_t s=0;s<(1U<<18);++s)if(sr::partialPatternPossible(s,18,positions[pos].cells,25))cpu.push_back(s);
|
||||||
|
for(auto j:referenceJobs)if(j.position==pos)gpu.push_back(j.root);
|
||||||
|
if(cpu!=gpu)throw std::runtime_error("CPU/GPU roots mismatch");
|
||||||
|
}
|
||||||
|
std::map<uint64_t,std::vector<uint32_t>> classes;
|
||||||
|
size_t offset=0;
|
||||||
|
for(size_t pos=0;pos<positions.size();++pos){
|
||||||
|
uint64_t key=(uint64_t(uint32_t(positions[pos].x)&65535)<<17)|(uint32_t(positions[pos].z)&131071);
|
||||||
|
std::vector<uint32_t> roots;
|
||||||
|
while(offset<referenceJobs.size()&&referenceJobs[offset].position==pos)roots.push_back(referenceJobs[offset++].root);
|
||||||
|
auto it=classes.find(key);
|
||||||
|
if(it==classes.end())classes.emplace(key,std::move(roots));
|
||||||
|
else if(it->second!=roots)throw std::runtime_error("periodic class root list mismatch");
|
||||||
|
}
|
||||||
|
std::cout<<"{\"type\":\"periodic_validation\",\"classes\":"<<classes.size()<<",\"positions\":"<<positions.size()<<",\"equal\":true}"<<std::endl;
|
||||||
|
std::cout<<"{\"type\":\"root_counts\",\"counts\":[";for(size_t i=0;i<referenceCounts.size();++i){if(i)std::cout<<",";std::cout<<referenceCounts[i];}std::cout<<"]}"<<std::endl;
|
||||||
|
std::cout<<"{\"type\":\"process\",\"ms\":"<<std::chrono::duration<double,std::milli>(Clock::now()-processStart).count()<<"}"<<std::endl;
|
||||||
|
}catch(const std::exception&e){std::cerr<<e.what()<<"\n";return 1;}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
#pragma once
|
||||||
|
// Compatibility include for historical experiment entrypoints.
|
||||||
|
#include "../../../include/slime/upper_interval.hpp"
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
20508 90242
|
||||||
|
54865 12632
|
||||||
|
57808 83448
|
||||||
|
45571 7025
|
||||||
|
41354 55843
|
||||||
|
17402 110862
|
||||||
|
8259 107266
|
||||||
|
43482 107388
|
||||||
|
61898 108538
|
||||||
|
58072 45380
|
||||||
|
2384 37823
|
||||||
|
56566 33756
|
||||||
|
54050 33180
|
||||||
|
48027 41698
|
||||||
|
26400 127463
|
||||||
|
13969 27082
|
||||||
|
63274 45862
|
||||||
|
51826 109902
|
||||||
|
35332 113389
|
||||||
|
55589 46936
|
||||||
|
6571 80653
|
||||||
|
40963 108250
|
||||||
|
8207 115527
|
||||||
|
65432 46739
|
||||||
|
1632 44691
|
||||||
|
14689 90828
|
||||||
|
13769 13900
|
||||||
|
45145 91692
|
||||||
|
57059 84915
|
||||||
|
19692 116595
|
||||||
|
65533 67472
|
||||||
|
49762 93725
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
51069 419124
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
[1596, 1653, 1596, 1653, 1596, 1653, 1653, 1653, 1653, 1596, 1653, 1653, 1653, 1596, 1682, 1653, 1596, 1653, 1682, 1596, 1596, 1653, 1653, 1596, 1596, 1596, 1653, 1653, 1596, 1653, 1596, 1653]
|
||||||
52213
research/experiments/full_search_feasibility/positions/lifts.txt
Normal file
52213
research/experiments/full_search_feasibility/positions/lifts.txt
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
65536
research/experiments/full_search_feasibility/positions/random65536.txt
Normal file
65536
research/experiments/full_search_feasibility/positions/random65536.txt
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,21 @@
|
||||||
|
51069 419124
|
||||||
|
51069 419125
|
||||||
|
51070 419124
|
||||||
|
51068 419124
|
||||||
|
51069 419123
|
||||||
|
51071 419124
|
||||||
|
51069 419126
|
||||||
|
51071 419126
|
||||||
|
51067 419122
|
||||||
|
51068 419125
|
||||||
|
51070 419123
|
||||||
|
51072 419124
|
||||||
|
51069 419127
|
||||||
|
51072 419127
|
||||||
|
51066 419124
|
||||||
|
51069 419121
|
||||||
|
0 0
|
||||||
|
-1875000 -1875000
|
||||||
|
1874995 1874995
|
||||||
|
2147483643 2147483643
|
||||||
|
-2147483648 -2147483648
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
51069 419124
|
||||||
|
-151639 -947718
|
||||||
|
1628807 1768848
|
||||||
|
183149 -1355854
|
||||||
|
399809 180074
|
||||||
|
1331359 -949016
|
||||||
|
-928444 -1623457
|
||||||
|
-434111 1380151
|
||||||
|
152758 -903569
|
||||||
|
1573808 123536
|
||||||
|
1439373 -1760847
|
||||||
|
751203 -1435166
|
||||||
|
610332 1532034
|
||||||
|
-1825025 -1355719
|
||||||
|
-115982 205535
|
||||||
|
1824337 -1691304
|
||||||
|
653615 -1196891
|
||||||
|
70982 -1096949
|
||||||
|
471686 101463
|
||||||
|
-1283834 -394356
|
||||||
|
-1077237 -730018
|
||||||
|
1639252 1254113
|
||||||
|
798574 629823
|
||||||
|
27251 -1281168
|
||||||
|
187989 -364498
|
||||||
|
-1749840 1146587
|
||||||
|
800514 -1729353
|
||||||
|
-1222320 1829300
|
||||||
|
-21017 1801614
|
||||||
|
381104 -1778790
|
||||||
|
-913761 1285921
|
||||||
|
-1326869 -862681
|
||||||
|
753606 -1698600
|
||||||
|
861651 1158886
|
||||||
|
-853922 -1012344
|
||||||
|
409724 1445621
|
||||||
|
820384 -678458
|
||||||
|
103226 1272878
|
||||||
|
1600737 263564
|
||||||
|
-506130 -1245993
|
||||||
|
-825723 449811
|
||||||
|
-1683685 -40291
|
||||||
|
-1566877 1270725
|
||||||
|
-1224693 -1550920
|
||||||
|
-643224 416976
|
||||||
|
1637732 1161249
|
||||||
|
-540219 629696
|
||||||
|
385799 1463828
|
||||||
|
1235472 -549937
|
||||||
|
1456601 -972438
|
||||||
|
416534 -1635641
|
||||||
|
-1301541 -1746936
|
||||||
|
-1594959 -463516
|
||||||
|
1204858 -33676
|
||||||
|
-764975 64792
|
||||||
|
-123655 -117892
|
||||||
|
-255226 -926872
|
||||||
|
838204 1645544
|
||||||
|
1859060 -694124
|
||||||
|
-161268 -914208
|
||||||
|
-1466503 385080
|
||||||
|
411691 -631360
|
||||||
|
1613179 175007
|
||||||
|
-1629505 -215899
|
||||||
|
-1766803 1662240
|
||||||
|
-298802 1780132
|
||||||
|
-966039 -1151326
|
||||||
|
-1397810 1758470
|
||||||
|
-1180783 -884455
|
||||||
|
-1857260 -966834
|
||||||
|
1432151 1219727
|
||||||
|
-1042318 166210
|
||||||
|
1695758 1454030
|
||||||
|
1282751 1461553
|
||||||
|
1047318 -175482
|
||||||
|
-1264922 -901360
|
||||||
|
1361879 -1342934
|
||||||
|
-190193 1035186
|
||||||
|
786218 382124
|
||||||
|
-441325 310526
|
||||||
|
274023 1368323
|
||||||
|
1005396 -1205652
|
||||||
|
1345022 -242369
|
||||||
|
1362954 -120311
|
||||||
|
688497 -1303174
|
||||||
|
184270 -1475672
|
||||||
|
1697223 -841268
|
||||||
|
909342 1403124
|
||||||
|
-518834 -1841162
|
||||||
|
-457099 -712327
|
||||||
|
-1061496 -1132296
|
||||||
|
-1568524 209693
|
||||||
|
862602 213688
|
||||||
|
725831 -374225
|
||||||
|
-955021 1598463
|
||||||
|
-1242843 706739
|
||||||
|
-928275 1211740
|
||||||
|
-97386 26812
|
||||||
|
-159596 -898691
|
||||||
|
559750 -919322
|
||||||
|
-1261526 -1170283
|
||||||
|
1217562 -1802980
|
||||||
|
-273908 -438569
|
||||||
|
948984 915311
|
||||||
|
-1190015 -773822
|
||||||
|
500811 -295186
|
||||||
|
-489265 1081569
|
||||||
|
1816094 -1831463
|
||||||
|
1168538 105449
|
||||||
|
1031166 1699672
|
||||||
|
319995 -1076966
|
||||||
|
-723529 -385152
|
||||||
|
-1043111 1661566
|
||||||
|
1503451 -1847764
|
||||||
|
477739 -899398
|
||||||
|
-697019 1039008
|
||||||
|
1089311 1472213
|
||||||
|
1205717 566995
|
||||||
|
-1189155 303581
|
||||||
|
75335 964912
|
||||||
|
1716857 -1068302
|
||||||
|
-1646128 1525240
|
||||||
|
-526278 1172893
|
||||||
|
-478717 -1696911
|
||||||
|
-1648970 1053068
|
||||||
|
-294273 -392369
|
||||||
|
-341537 1585086
|
||||||
|
431026 -1312089
|
||||||
254976
research/experiments/full_search_feasibility/results/all.json
Normal file
254976
research/experiments/full_search_feasibility/results/all.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,14 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":32}
|
||||||
|
{"type":"root_timing","mode":0,"round":0,"kernel_ms":0.1382399946}
|
||||||
|
{"type":"root_timing","mode":1,"round":0,"kernel_ms":0.2088959962}
|
||||||
|
{"type":"root_timing","mode":1,"round":1,"kernel_ms":0.2088959962}
|
||||||
|
{"type":"root_timing","mode":0,"round":1,"kernel_ms":0.1351680011}
|
||||||
|
{"type":"root_timing","mode":0,"round":2,"kernel_ms":0.1382399946}
|
||||||
|
{"type":"root_timing","mode":1,"round":2,"kernel_ms":0.2037760019}
|
||||||
|
{"type":"root_timing","mode":1,"round":3,"kernel_ms":0.2283519953}
|
||||||
|
{"type":"root_timing","mode":0,"round":3,"kernel_ms":0.1648640037}
|
||||||
|
{"type":"root_timing","mode":0,"round":4,"kernel_ms":0.1587200016}
|
||||||
|
{"type":"root_timing","mode":1,"round":4,"kernel_ms":0.2088959962}
|
||||||
|
{"type":"periodic_validation","classes":32,"positions":32,"equal":true}
|
||||||
|
{"type":"root_counts","counts":[21,1,4,40,24,1,1,91,3,47,16,15,4,412,1,14,3,5,28,1,2,1,4,7,2,9,34,1,1,3,226,28]}
|
||||||
|
{"type":"process","ms":249.778637}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,10 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":21}
|
||||||
|
{"type":"complete_timing","round":0,"first_ms":1.288192034,"tail_ms":0.7680000067,"compute_ms":2.056191921,"scan_ms":2.294025,"matches":24}
|
||||||
|
{"type":"complete_timing","round":1,"first_ms":1.290240049,"tail_ms":0.7680000067,"compute_ms":2.058239937,"scan_ms":2.245116,"matches":24}
|
||||||
|
{"type":"complete_timing","round":2,"first_ms":1.289216042,"tail_ms":0.7680000067,"compute_ms":2.057216167,"scan_ms":2.231547,"matches":24}
|
||||||
|
{"type":"complete_timing","round":3,"first_ms":1.290240049,"tail_ms":0.7680000067,"compute_ms":2.058239937,"scan_ms":2.249119,"matches":24}
|
||||||
|
{"type":"complete_timing","round":4,"first_ms":1.289216042,"tail_ms":0.7690240145,"compute_ms":2.058239937,"scan_ms":2.262982,"matches":24}
|
||||||
|
{"type":"complete_info","root_ms":1.327103972,"jobs":474,"matches":[[10358701894766,51069,419124],[15959335054446,51069,419124],[18914276748398,51069,419124],[24514909908078,51069,419124],[27469851602030,51069,419124],[33070484761710,51069,419124],[36025426455662,51069,419124],[41626059615342,51069,419124],[44581001309294,51069,419124],[50181634468974,51069,419124],[53136576162926,51069,419124],[58737209322606,51069,419124],[61692151016558,51069,419124],[67292784176238,51069,419124],[70247725870190,51069,419124],[75848359029870,51069,419124],[78803300723822,51069,419124],[84403933883502,51069,419124],[87358875577454,51069,419124],[92959508737134,51069,419124],[95914450431086,51069,419124],[101515083590766,51069,419124],[104470025284718,51069,419124],[110070658444398,51069,419124]]}
|
||||||
|
{"type":"periodic_validation","classes":20,"positions":21,"equal":true}
|
||||||
|
{"type":"root_counts","counts":[84,152,16,0,2,20,61,22,0,1,35,30,20,29,0,2,0,0,0,0,0]}
|
||||||
|
{"type":"process","ms":271.397062}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"upper_planes":4096,"direct_seeds":33554432,"tail_states":1048576,"tail_slime":104848,"odd_tail_slime":52422,"periodicity_cells":250000,"passed":true}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"independent_python_java_verification": true,
|
||||||
|
"unique_verified_matches": 24
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":1}
|
||||||
|
{"type":"search_timing","mode":0,"round":0,"kernel_ms":65.88722992,"scan_ms":64.322731,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":0,"kernel_ms":0.7966719866,"scan_ms":0.989482,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":0,"kernel_ms":0.3901439905,"scan_ms":0.568541,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":0,"kernel_ms":0.3235839903,"scan_ms":0.475938,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":1,"kernel_ms":0.7956479788,"scan_ms":0.931277,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":1,"kernel_ms":0.3921920061,"scan_ms":0.537933,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":1,"kernel_ms":0.318464011,"scan_ms":0.484489,"matches":24}
|
||||||
|
{"type":"search_timing","mode":0,"round":1,"kernel_ms":67.21331024,"scan_ms":65.89873,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":2,"kernel_ms":0.3983359933,"scan_ms":0.656092,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":2,"kernel_ms":0.3235839903,"scan_ms":0.448731,"matches":24}
|
||||||
|
{"type":"search_timing","mode":0,"round":2,"kernel_ms":63.08863831,"scan_ms":61.613729,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":2,"kernel_ms":0.7587839961,"scan_ms":0.978891,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":3,"kernel_ms":0.5212159753,"scan_ms":1.030974,"matches":24}
|
||||||
|
{"type":"search_timing","mode":0,"round":3,"kernel_ms":62.64831924,"scan_ms":61.355937,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":3,"kernel_ms":0.7587839961,"scan_ms":0.913884,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":3,"kernel_ms":0.3696640134,"scan_ms":0.516653,"matches":24}
|
||||||
|
{"type":"search_timing","mode":0,"round":4,"kernel_ms":63.75014496,"scan_ms":62.243002,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":4,"kernel_ms":0.7628800273,"scan_ms":0.910191,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":4,"kernel_ms":0.3768320084,"scan_ms":0.498385,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":4,"kernel_ms":0.3102720082,"scan_ms":0.436487,"matches":24}
|
||||||
|
{"type":"search_info","root_ms":0.8509439826,"jobs":84,"lower35":11010048,"matches":[[10358701894766,51069,419124],[15959335054446,51069,419124],[18914276748398,51069,419124],[24514909908078,51069,419124],[27469851602030,51069,419124],[33070484761710,51069,419124],[36025426455662,51069,419124],[41626059615342,51069,419124],[44581001309294,51069,419124],[50181634468974,51069,419124],[53136576162926,51069,419124],[58737209322606,51069,419124],[61692151016558,51069,419124],[67292784176238,51069,419124],[70247725870190,51069,419124],[75848359029870,51069,419124],[78803300723822,51069,419124],[84403933883502,51069,419124],[87358875577454,51069,419124],[92959508737134,51069,419124],[95914450431086,51069,419124],[101515083590766,51069,419124],[104470025284718,51069,419124],[110070658444398,51069,419124]]}
|
||||||
|
{"type":"periodic_validation","classes":1,"positions":1,"equal":true}
|
||||||
|
{"type":"root_counts","counts":[84]}
|
||||||
|
{"type":"process","ms":618.005189}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,25 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":21}
|
||||||
|
{"type":"search_timing","mode":0,"round":0,"kernel_ms":379.4073486,"scan_ms":368.969147,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":0,"kernel_ms":3.854336023,"scan_ms":3.911966,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":0,"kernel_ms":1.691648006,"scan_ms":1.808333,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":0,"kernel_ms":1.225728035,"scan_ms":1.328216,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":1,"kernel_ms":4.673535824,"scan_ms":4.759871,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":1,"kernel_ms":2.663424015,"scan_ms":2.865543,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":1,"kernel_ms":1.217535973,"scan_ms":1.325398,"matches":24}
|
||||||
|
{"type":"search_timing","mode":0,"round":1,"kernel_ms":371.9034729,"scan_ms":362.378901,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":2,"kernel_ms":1.757184029,"scan_ms":1.912097,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":2,"kernel_ms":1.245183945,"scan_ms":1.355726,"matches":24}
|
||||||
|
{"type":"search_timing","mode":0,"round":2,"kernel_ms":363.8763428,"scan_ms":356.104291,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":2,"kernel_ms":3.95571208,"scan_ms":4.099446,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":3,"kernel_ms":2.305023909,"scan_ms":2.485873,"matches":24}
|
||||||
|
{"type":"search_timing","mode":0,"round":3,"kernel_ms":361.4719849,"scan_ms":353.99145,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":3,"kernel_ms":5.737472057,"scan_ms":5.931658,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":3,"kernel_ms":1.793023944,"scan_ms":2.044199,"matches":24}
|
||||||
|
{"type":"search_timing","mode":0,"round":4,"kernel_ms":361.7187805,"scan_ms":354.316081,"matches":24}
|
||||||
|
{"type":"search_timing","mode":1,"round":4,"kernel_ms":5.419007778,"scan_ms":5.597639,"matches":24}
|
||||||
|
{"type":"search_timing","mode":2,"round":4,"kernel_ms":1.728512049,"scan_ms":1.845705,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":4,"kernel_ms":1.251327991,"scan_ms":1.363353,"matches":24}
|
||||||
|
{"type":"search_info","root_ms":0.79974401,"jobs":474,"lower35":62128128,"matches":[[10358701894766,51069,419124],[15959335054446,51069,419124],[18914276748398,51069,419124],[24514909908078,51069,419124],[27469851602030,51069,419124],[33070484761710,51069,419124],[36025426455662,51069,419124],[41626059615342,51069,419124],[44581001309294,51069,419124],[50181634468974,51069,419124],[53136576162926,51069,419124],[58737209322606,51069,419124],[61692151016558,51069,419124],[67292784176238,51069,419124],[70247725870190,51069,419124],[75848359029870,51069,419124],[78803300723822,51069,419124],[84403933883502,51069,419124],[87358875577454,51069,419124],[92959508737134,51069,419124],[95914450431086,51069,419124],[101515083590766,51069,419124],[104470025284718,51069,419124],[110070658444398,51069,419124]]}
|
||||||
|
{"type":"periodic_validation","classes":20,"positions":21,"equal":true}
|
||||||
|
{"type":"root_counts","counts":[84,152,16,0,2,20,61,22,0,1,35,30,20,29,0,2,0,0,0,0,0]}
|
||||||
|
{"type":"process","ms":2434.177987}
|
||||||
|
|
@ -0,0 +1,499 @@
|
||||||
|
{
|
||||||
|
"known": {
|
||||||
|
"kernel_median_ms": {
|
||||||
|
"0": 63.75014496,
|
||||||
|
"1": 0.7628800273,
|
||||||
|
"2": 0.3901439905,
|
||||||
|
"3": 0.3235839903
|
||||||
|
},
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 0.8509439826,
|
||||||
|
"jobs": 84,
|
||||||
|
"lower35": 11010048,
|
||||||
|
"matches": [
|
||||||
|
[
|
||||||
|
10358701894766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15959335054446,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18914276748398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
24514909908078,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27469851602030,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33070484761710,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36025426455662,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41626059615342,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44581001309294,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50181634468974,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53136576162926,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
58737209322606,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61692151016558,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67292784176238,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
70247725870190,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75848359029870,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78803300723822,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84403933883502,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87358875577454,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
92959508737134,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
95914450431086,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
101515083590766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
104470025284718,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
110070658444398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"kernel_median_ms": {
|
||||||
|
"0": 363.8763428,
|
||||||
|
"1": 4.673535824,
|
||||||
|
"2": 1.757184029,
|
||||||
|
"3": 1.245183945
|
||||||
|
},
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 0.79974401,
|
||||||
|
"jobs": 474,
|
||||||
|
"lower35": 62128128,
|
||||||
|
"matches": [
|
||||||
|
[
|
||||||
|
10358701894766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15959335054446,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18914276748398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
24514909908078,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27469851602030,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33070484761710,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36025426455662,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41626059615342,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44581001309294,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50181634468974,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53136576162926,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
58737209322606,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61692151016558,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67292784176238,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
70247725870190,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75848359029870,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78803300723822,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84403933883502,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87358875577454,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
92959508737134,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
95914450431086,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
101515083590766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
104470025284718,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
110070658444398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"random4096": {
|
||||||
|
"kernel_median_ms": {
|
||||||
|
"0": 2318.624756,
|
||||||
|
"1": 29.37958336,
|
||||||
|
"2": 12.29721642,
|
||||||
|
"3": 8.157183647
|
||||||
|
},
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 16.9953289,
|
||||||
|
"jobs": 2960,
|
||||||
|
"lower35": 387973120,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"random65536": {
|
||||||
|
"kernel_median_ms": {
|
||||||
|
"1": 407.6585083,
|
||||||
|
"2": 175.9743958,
|
||||||
|
"3": 131.6700134
|
||||||
|
},
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 268.6993713,
|
||||||
|
"jobs": 45356,
|
||||||
|
"lower35": 5944901632,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"distribution": {
|
||||||
|
"positions": 65536,
|
||||||
|
"roots": 45356,
|
||||||
|
"mean": 0.69207763671875,
|
||||||
|
"mean_normal95": [
|
||||||
|
0.6105416864300699,
|
||||||
|
0.7736135870074301
|
||||||
|
],
|
||||||
|
"zero_fraction": 0.9691619873046875,
|
||||||
|
"max": 1015
|
||||||
|
},
|
||||||
|
"roots65536": {
|
||||||
|
"0": 280.6394958,
|
||||||
|
"1": 380.5327454
|
||||||
|
},
|
||||||
|
"classes32": {
|
||||||
|
"0": 0.1382399946,
|
||||||
|
"1": 0.2088959962
|
||||||
|
},
|
||||||
|
"lifts": {
|
||||||
|
"0": 293.8664856,
|
||||||
|
"1": 409.9522705
|
||||||
|
},
|
||||||
|
"tail_one_cell": {
|
||||||
|
"kernel_median_ms": {
|
||||||
|
"0": 0.03174399957,
|
||||||
|
"1": 0.02559999935
|
||||||
|
},
|
||||||
|
"matches": 104848
|
||||||
|
},
|
||||||
|
"tail_two_cells": {
|
||||||
|
"kernel_median_ms": {
|
||||||
|
"0": 0.06451199949,
|
||||||
|
"1": 0.0532479994
|
||||||
|
},
|
||||||
|
"matches": 20888
|
||||||
|
},
|
||||||
|
"tail_five_cells": {
|
||||||
|
"kernel_median_ms": {
|
||||||
|
"0": 0.1392640024,
|
||||||
|
"1": 0.08396799862
|
||||||
|
},
|
||||||
|
"matches": 109
|
||||||
|
},
|
||||||
|
"tail128": {
|
||||||
|
"kernel_median_ms": {
|
||||||
|
"0": 93.59667206,
|
||||||
|
"1": 3.907583952
|
||||||
|
},
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"projections": {
|
||||||
|
"world_anchors": 14062500000000,
|
||||||
|
"lower35_visits": 1.2756375e+18,
|
||||||
|
"first_draw_kernel_years": 0.8952936774216201,
|
||||||
|
"root_no_reuse_years": 1.908215543817446,
|
||||||
|
"root_class_reuse_hours": 10.217772220416,
|
||||||
|
"canonical_tail_kernel_years": 13.603695931457478,
|
||||||
|
"complete_compute_years": 14.498989608879096,
|
||||||
|
"complete_same_batch_kernel_years": 16.409789423895784,
|
||||||
|
"complete_same_batch_scan_years": 16.024280153342424
|
||||||
|
},
|
||||||
|
"complete_stress": {
|
||||||
|
"first_ms": 1.289216042,
|
||||||
|
"tail_ms": 0.7680000067,
|
||||||
|
"compute_ms": 2.058239937,
|
||||||
|
"scan_ms": 2.249119,
|
||||||
|
"type": "complete_info",
|
||||||
|
"root_ms": 1.327103972,
|
||||||
|
"jobs": 474,
|
||||||
|
"matches": [
|
||||||
|
[
|
||||||
|
10358701894766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15959335054446,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18914276748398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
24514909908078,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27469851602030,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33070484761710,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36025426455662,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41626059615342,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44581001309294,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50181634468974,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53136576162926,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
58737209322606,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61692151016558,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67292784176238,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
70247725870190,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75848359029870,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78803300723822,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84403933883502,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87358875577454,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
92959508737134,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
95914450431086,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
101515083590766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
104470025284718,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
110070658444398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"complete65536": {
|
||||||
|
"first_ms": 129.1571198,
|
||||||
|
"tail_ms": 2283.081787,
|
||||||
|
"compute_ms": 2413.372559,
|
||||||
|
"scan_ms": 2356.676067,
|
||||||
|
"type": "complete_info",
|
||||||
|
"root_ms": 296.572937,
|
||||||
|
"jobs": 45356,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"cache_counts_equal": true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":128}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":25,"round":0,"kernel_ms":93.59667206,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":25,"round":0,"kernel_ms":3.907583952,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":25,"round":1,"kernel_ms":3.909631968,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":25,"round":1,"kernel_ms":93.7738266,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":25,"round":2,"kernel_ms":92.06886292,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":25,"round":2,"kernel_ms":5.363711834,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":25,"round":3,"kernel_ms":3.903487921,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":25,"round":3,"kernel_ms":91.83436584,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":25,"round":4,"kernel_ms":93.8915863,"matches":0}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":25,"round":4,"kernel_ms":3.904511929,"matches":0}
|
||||||
|
{"type":"process","ms":778.407359}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":1}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":5,"round":0,"kernel_ms":0.1402879953,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":5,"round":0,"kernel_ms":0.08499199897,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":5,"round":1,"kernel_ms":0.08396799862,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":5,"round":1,"kernel_ms":0.1392640024,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":5,"round":2,"kernel_ms":0.1392640024,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":5,"round":2,"kernel_ms":0.08499199897,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":5,"round":3,"kernel_ms":0.08396799862,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":5,"round":3,"kernel_ms":0.1351680011,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":5,"round":4,"kernel_ms":0.1392640024,"matches":109}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":5,"round":4,"kernel_ms":0.08396799862,"matches":109}
|
||||||
|
{"type":"process","ms":199.03863}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":1}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":1,"round":0,"kernel_ms":0.03174399957,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":1,"round":0,"kernel_ms":0.02559999935,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":1,"round":1,"kernel_ms":0.02559999935,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":1,"round":1,"kernel_ms":0.03174399957,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":1,"round":2,"kernel_ms":0.0308480002,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":1,"round":2,"kernel_ms":0.0266239997,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":1,"round":3,"kernel_ms":0.02559999935,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":1,"round":3,"kernel_ms":0.03190400079,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":1,"round":4,"kernel_ms":0.03174399957,"matches":104848}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":1,"round":4,"kernel_ms":0.02559999935,"matches":104848}
|
||||||
|
{"type":"process","ms":294.256955}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":1}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":2,"round":0,"kernel_ms":0.06451199949,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":2,"round":0,"kernel_ms":0.05017599836,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":2,"round":1,"kernel_ms":0.0532479994,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":2,"round":1,"kernel_ms":0.06451199949,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":2,"round":2,"kernel_ms":0.06553599983,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":2,"round":2,"kernel_ms":0.05734400079,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":2,"round":3,"kernel_ms":0.0532479994,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":2,"round":3,"kernel_ms":0.06041600183,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":0,"cells":2,"round":4,"kernel_ms":0.06656000018,"matches":20888}
|
||||||
|
{"type":"tail_timing","mode":1,"cells":2,"round":4,"kernel_ms":0.04915200174,"matches":20888}
|
||||||
|
{"type":"process","ms":224.394511}
|
||||||
195
research/experiments/full_search_feasibility/run.py
Normal file
195
research/experiments/full_search_feasibility/run.py
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Reproducible CPU validation and sequential CUDA experiments (no network)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import platform
|
||||||
|
import random
|
||||||
|
import statistics
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
POS = HERE / "positions"
|
||||||
|
OUT = HERE / "results"
|
||||||
|
BIN = "/tmp/slime_feasibility_cuda"
|
||||||
|
|
||||||
|
|
||||||
|
def save_positions(name, points):
|
||||||
|
(POS / f"{name}.txt").write_text("".join(f"{x} {z}\n" for x, z in points))
|
||||||
|
|
||||||
|
|
||||||
|
def prepare():
|
||||||
|
POS.mkdir(exist_ok=True)
|
||||||
|
OUT.mkdir(exist_ok=True)
|
||||||
|
save_positions("known", [(51069, 419124)])
|
||||||
|
offsets = [(0, 0), (0, 1), (1, 0), (-1, 0), (0, -1), (2, 0), (0, 2),
|
||||||
|
(2, 2), (-2, -2), (-1, 1), (1, -1), (3, 0), (0, 3), (3, 3),
|
||||||
|
(-3, 0), (0, -3)]
|
||||||
|
save_positions("stress", [(51069 + x, 419124 + z) for x, z in offsets] +
|
||||||
|
[(0, 0), (-1875000, -1875000), (1874995, 1874995),
|
||||||
|
(2147483643, 2147483643), (-2147483648, -2147483648)])
|
||||||
|
rng = random.Random(2026101101)
|
||||||
|
coords = [(rng.randrange(-1875000, 1875000), rng.randrange(-1875000, 1875000))
|
||||||
|
for _ in range(65536)]
|
||||||
|
save_positions("random4096", coords[:4096])
|
||||||
|
save_positions("random65536", coords)
|
||||||
|
save_positions("tail128", [(51069, 419124)] + coords[:127])
|
||||||
|
return coords
|
||||||
|
|
||||||
|
|
||||||
|
def run_case(name, positions, task, repeats=5, cells=None):
|
||||||
|
cmd = [BIN, str(POS / f"{positions}.txt"), task, str(repeats)]
|
||||||
|
if cells is not None:
|
||||||
|
cmd.append(str(cells))
|
||||||
|
start = time.perf_counter()
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||||
|
(OUT / f"{name}.jsonl").write_text(result.stdout)
|
||||||
|
(OUT / f"{name}.stderr").write_text(result.stderr)
|
||||||
|
if result.returncode:
|
||||||
|
raise RuntimeError(f"{name} failed: {result.stderr}")
|
||||||
|
rows = [json.loads(line) for line in result.stdout.splitlines()]
|
||||||
|
record = {"command": cmd, "wall_ms": (time.perf_counter() - start) * 1000,
|
||||||
|
"rows": rows}
|
||||||
|
print(json.dumps({"finished": name, "wall_ms": record["wall_ms"]}), flush=True)
|
||||||
|
return record
|
||||||
|
|
||||||
|
|
||||||
|
def median(rows, kind, mode):
|
||||||
|
return statistics.median(r["kernel_ms"] for r in rows
|
||||||
|
if r["type"] == kind and r["mode"] == mode)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--analyse-only", action="store_true")
|
||||||
|
ap.add_argument("--complete-only", action="store_true")
|
||||||
|
args = ap.parse_args()
|
||||||
|
if args.analyse_only or args.complete_only:
|
||||||
|
records = json.loads((OUT / "all.json").read_text())
|
||||||
|
else:
|
||||||
|
coords = prepare()
|
||||||
|
subprocess.run(["nvcc", "-O3", "-std=c++17", "-arch=sm_86",
|
||||||
|
str(HERE / "cuda_bench.cu"), "-o", BIN], check=True)
|
||||||
|
subprocess.run(["g++", "-O3", "-std=c++17", "-march=native", "-Wall", "-Wextra",
|
||||||
|
str(HERE / "cpu_verify.cpp"), "-o", "/tmp/slime_feasibility_verify"], check=True)
|
||||||
|
cpu = subprocess.check_output(["/tmp/slime_feasibility_verify"], text=True)
|
||||||
|
(OUT / "cpu_verify.json").write_text(cpu)
|
||||||
|
gpu_query = ["nvidia-smi", "--query-gpu=name,driver_version,temperature.gpu,utilization.gpu", "--format=csv"]
|
||||||
|
records = {"metadata": {
|
||||||
|
"date": datetime.now(ZoneInfo("Asia/Hong_Kong")).isoformat(),
|
||||||
|
"platform": platform.platform(), "cpu_validation": json.loads(cpu),
|
||||||
|
"gpu_before": subprocess.check_output(gpu_query, text=True),
|
||||||
|
"source_sha256": {p.name: hashlib.sha256(p.read_bytes()).hexdigest()
|
||||||
|
for p in [HERE / "cuda_bench.cu", HERE / "interval_solver.hpp",
|
||||||
|
HERE / "cpu_verify.cpp", HERE / "run.py",
|
||||||
|
HERE.parent.parent / "slime_solver_core.hpp",
|
||||||
|
HERE.parents[2] / "include/slime/solver_core.hpp",
|
||||||
|
HERE.parents[2] / "include/slime/upper_interval.hpp"]}}}
|
||||||
|
|
||||||
|
def record(name, positions, task, repeats=5, cells=None):
|
||||||
|
records[name] = run_case(name, positions, task, repeats, cells)
|
||||||
|
(OUT / "all.json").write_text(json.dumps(records, indent=2))
|
||||||
|
|
||||||
|
record("known", "known", "compare")
|
||||||
|
record("stress", "stress", "compare")
|
||||||
|
record("random4096", "random4096", "compare")
|
||||||
|
record("random65536", "random65536", "fast")
|
||||||
|
record("roots65536", "random65536", "root")
|
||||||
|
counts = next(r["counts"] for r in records["random4096"]["rows"] if r["type"] == "root_counts")
|
||||||
|
classes = [(x % 65536, z % 131072) for (x, z), n in zip(coords, counts) if n][:32]
|
||||||
|
save_positions("classes32", classes)
|
||||||
|
lifts, groups = [], []
|
||||||
|
for x, z in classes:
|
||||||
|
current = [(xx, zz)
|
||||||
|
for xx in range(-1875000 + (x + 1875000) % 65536, 1875000, 65536)
|
||||||
|
for zz in range(-1875000 + (z + 1875000) % 131072, 1875000, 131072)]
|
||||||
|
groups.append(len(current))
|
||||||
|
lifts.extend(current)
|
||||||
|
save_positions("lifts", lifts)
|
||||||
|
(POS / "lift_groups.json").write_text(json.dumps(groups))
|
||||||
|
record("classes32", "classes32", "root")
|
||||||
|
record("lifts", "lifts", "root")
|
||||||
|
record("tail_one_cell", "known", "tail", cells=1)
|
||||||
|
record("tail_two_cells", "known", "tail", cells=2)
|
||||||
|
record("tail_five_cells", "known", "tail", cells=5)
|
||||||
|
record("tail128", "tail128", "tail")
|
||||||
|
records["metadata"]["gpu_after"] = subprocess.check_output(gpu_query, text=True)
|
||||||
|
(OUT / "all.json").write_text(json.dumps(records, indent=2))
|
||||||
|
|
||||||
|
if not args.analyse_only:
|
||||||
|
if args.complete_only:
|
||||||
|
subprocess.run(["nvcc", "-O3", "-std=c++17", "-arch=sm_86",
|
||||||
|
str(HERE / "cuda_bench.cu"), "-o", BIN], check=True)
|
||||||
|
records["complete_stress"] = run_case("complete_stress", "stress", "complete")
|
||||||
|
records["complete65536"] = run_case("complete65536", "random65536", "complete")
|
||||||
|
records["metadata"]["complete_source_sha256"] = {
|
||||||
|
p.name: hashlib.sha256(p.read_bytes()).hexdigest()
|
||||||
|
for p in [HERE / "cuda_bench.cu", HERE / "interval_solver.hpp", HERE / "run.py",
|
||||||
|
HERE.parents[2] / "include/slime/solver_core.hpp",
|
||||||
|
HERE.parents[2] / "include/slime/upper_interval.hpp"]}
|
||||||
|
(OUT / "all.json").write_text(json.dumps(records, indent=2))
|
||||||
|
|
||||||
|
summary = {}
|
||||||
|
for name in ["known", "stress", "random4096", "random65536"]:
|
||||||
|
rows = records[name]["rows"]
|
||||||
|
modes = sorted({r["mode"] for r in rows if r["type"] == "search_timing"})
|
||||||
|
summary[name] = {"kernel_median_ms": {str(m): median(rows, "search_timing", m) for m in modes}}
|
||||||
|
summary[name].update(next(r for r in rows if r["type"] == "search_info"))
|
||||||
|
counts = next(r["counts"] for r in records["random65536"]["rows"] if r["type"] == "root_counts")
|
||||||
|
n = len(counts)
|
||||||
|
mean = statistics.mean(counts)
|
||||||
|
sem = statistics.stdev(counts) / n**0.5
|
||||||
|
summary["distribution"] = {"positions": n, "roots": sum(counts), "mean": mean,
|
||||||
|
"mean_normal95": [mean - 1.96 * sem, mean + 1.96 * sem],
|
||||||
|
"zero_fraction": counts.count(0) / n, "max": max(counts)}
|
||||||
|
for name in ["roots65536", "classes32", "lifts"]:
|
||||||
|
summary[name] = {str(m): median(records[name]["rows"], "root_timing", m) for m in [0, 1]}
|
||||||
|
for name in ["tail_one_cell", "tail_two_cells", "tail_five_cells", "tail128"]:
|
||||||
|
rows = records[name]["rows"]
|
||||||
|
summary[name] = {"kernel_median_ms": {str(m): median(rows, "tail_timing", m) for m in [0, 1]},
|
||||||
|
"matches": next(r["matches"] for r in rows if r["type"] == "tail_timing")}
|
||||||
|
|
||||||
|
# All projections are throughput extrapolations, not measured full-world scans.
|
||||||
|
world = 3750000**2
|
||||||
|
year = 365.25 * 86400
|
||||||
|
fast_ms = summary["random65536"]["kernel_median_ms"]["3"]
|
||||||
|
tail_ms = summary["tail128"]["kernel_median_ms"]["1"]
|
||||||
|
root_ms = min(summary["roots65536"].values())
|
||||||
|
summary["projections"] = {
|
||||||
|
"world_anchors": world,
|
||||||
|
"lower35_visits": world * mean * 131072,
|
||||||
|
"first_draw_kernel_years": fast_ms / 1000 / n * world / year,
|
||||||
|
"root_no_reuse_years": root_ms / 1000 / n * world / year,
|
||||||
|
"root_class_reuse_hours": root_ms / 1000 / n * (2**33) / 3600,
|
||||||
|
"canonical_tail_kernel_years": tail_ms / 1000 / 128 * world / year,
|
||||||
|
"complete_compute_years": (fast_ms / 1000 / n + tail_ms / 1000 / 128) * world / year,
|
||||||
|
}
|
||||||
|
for name in ["complete_stress", "complete65536"]:
|
||||||
|
if name in records:
|
||||||
|
rows = records[name]["rows"]
|
||||||
|
summary[name] = {key: statistics.median(r[key] for r in rows if r["type"] == "complete_timing")
|
||||||
|
for key in ["first_ms", "tail_ms", "compute_ms", "scan_ms"]}
|
||||||
|
summary[name].update(next(r for r in rows if r["type"] == "complete_info"))
|
||||||
|
if "complete65536" in summary:
|
||||||
|
summary["projections"]["complete_same_batch_kernel_years"] = summary["complete65536"]["compute_ms"] / 1000 / n * world / year
|
||||||
|
summary["projections"]["complete_same_batch_scan_years"] = summary["complete65536"]["scan_ms"] / 1000 / n * world / year
|
||||||
|
assert summary["complete_stress"]["matches"] == summary["stress"]["matches"]
|
||||||
|
# Count equality is checked here; the CUDA program additionally compares complete
|
||||||
|
# normalized root lists for every repeated coordinate class in its input.
|
||||||
|
base_counts = next(r["counts"] for r in records["classes32"]["rows"] if r["type"] == "root_counts")
|
||||||
|
lifted_counts = next(r["counts"] for r in records["lifts"]["rows"] if r["type"] == "root_counts")
|
||||||
|
groups = json.loads((POS / "lift_groups.json").read_text())
|
||||||
|
assert lifted_counts == [c for c, number in zip(base_counts, groups) for _ in range(number)]
|
||||||
|
summary["cache_counts_equal"] = True
|
||||||
|
(OUT / "summary.json").write_text(json.dumps(summary, indent=2))
|
||||||
|
print(json.dumps(summary, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
76
research/experiments/normal_search/PIPELINE_REPORT.md
Normal file
76
research/experiments/normal_search/PIPELINE_REPORT.md
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
# 普通搜索:坐标类复用及只展开有根类
|
||||||
|
|
||||||
|
2026-10-11(Asia/Hong_Kong);RTX 3060 Laptop GPU。接续 [16 候选成组筛选实验](REPORT.md)。补扫未修改、未运行。
|
||||||
|
|
||||||
|
结论:复用已接入可执行实验流程,计入分组、根生成、候选展开、必要传输、搜索和结果回传后仍有效。收益取决于同批坐标类重复率及有根类比例,不是统一倍数。对几乎无重复类的散点,直接成组筛选更快。
|
||||||
|
|
||||||
|
## 四条路径
|
||||||
|
|
||||||
|
- 模式 0:每个位置生成根,旧差过滤+区间求交。
|
||||||
|
- 模式 1:每个位置生成根,上一轮 16 候选成组筛选。
|
||||||
|
- 模式 2:坐标按 `(x mod 65536,z mod 131072)` 分类,每类生成一次根,再在 GPU 上展开任务,成组筛选。
|
||||||
|
- 模式 3:先分类、只构造代表位置;筛出类根后,仅构造和上传有根类的具体位置,再展开任务,成组筛选。
|
||||||
|
|
||||||
|
四条路径都重算本轮的类根,没有拿前一轮缓存假装免费。模式 2/3 使用各类排序后的完整根列表和位置偏移,在 GPU 上生成 `(具体位置,root)` 任务。模式 3 压缩位置后,将结果还原到原始输入索引。
|
||||||
|
|
||||||
|
程序检查根缓冲区和命中缓冲区容量,超限明确失败,不截断。单批支持最多 65,536 个位置、4,194,304 个展开后的根任务;较大任务仍需外层分批。当前是批次级调度实验,不是已经具备全世界断点续跑能力的正式扫描器。
|
||||||
|
|
||||||
|
## 实测批次耗时
|
||||||
|
|
||||||
|
预热一次、三轮轮换模式顺序。下表为 CPU 单调时钟测量的批次耗时中位数,单位 ms。
|
||||||
|
|
||||||
|
| 输入 | 位置数 / 类数 | 模式 0 | 模式 1 | 模式 2 | 模式 3 |
|
||||||
|
|---|---:|---:|---:|---:|---:|
|
||||||
|
| 已知位置所属类的全部世界内位置 | 1653 / 1 | 364.023 | 62.016 | 55.373 | 56.867 |
|
||||||
|
| 32 个有根类的全部世界内位置 | 52213 / 32 | 4691.311 | 915.446 | 656.710 | 676.742 |
|
||||||
|
| 512 个随机类,每类抽 64 个世界内位置 | 32768 / 512 | 172.222 | 141.138 | 14.235 | 9.586 |
|
||||||
|
| 4096 个均匀随机散点 | 4096 / 4096 | 24.022 | 17.902 | 19.239 | 19.502 |
|
||||||
|
|
||||||
|
**计入**:本批次位置项构造、分类、代表类筛根、类根回传/整理、偏移构造、必要 H2D 传输、GPU 根任务展开、每根公共项预计算、搜索、命中回传及排序、模式 3 结果索引还原。
|
||||||
|
|
||||||
|
**不计入**:输入文件读取、一次性 CUDA 初始化/工作区申请、专用于对照验证的完整任务回传和排序、日志打印。一次性准备另记为 `setup_ms`,这四批约 217~242 ms。预热复用了显存;新增预计算仍每轮执行。这里是长期运行时可摊薄初始化后的批次耗时,不是启动一次命令的进程总耗时。
|
||||||
|
|
||||||
|
本轮直接使用 host 时间,不与前一轮的 CUDA event 时间混算。
|
||||||
|
|
||||||
|
## 收益在哪里
|
||||||
|
|
||||||
|
以随机 512 类批次为例:
|
||||||
|
|
||||||
|
- 原先要为 32,768 个位置重复筛根;现在仅筛 512 个代表。
|
||||||
|
- 其中 19 个类有根,只有 1,216 个具体位置需要构造坐标项并传入后续搜索。
|
||||||
|
- 展开后的完整任务仍是相同的 15,424 个 `(位置,root)`,没有省略任何普通分支候选。
|
||||||
|
- 模式 3 的阶段中位数:初始分类/代表构造约 0.416 ms,类根生成约 2.049 ms,整理/展开约 0.690 ms,搜索及回传约 6.775 ms。各阶段中位数不必精确相加等于总耗时中位数。
|
||||||
|
- 相比上一轮成组筛选流程,本批再加速 14.72×;相对模式 0 为 17.97×。
|
||||||
|
|
||||||
|
32 个有根类的压力批次有 1,693,228 个根任务,全部类都必须展开,计算主要花在后续搜索。模式 3 相对模式 1 仅加速 1.35×;模式 2 略优,因为无需压缩位置和还原索引。
|
||||||
|
|
||||||
|
随机散点没有类复用收益,分类、回传和整理增加开销;模式 3 比模式 1 多耗时约 8.9%。因此应按类组织大范围遍历;不能对任意散点输入无条件启用该流程。
|
||||||
|
|
||||||
|
## 正确性和边界检查
|
||||||
|
|
||||||
|
每一轮、每一种模式均将**全部展开根任务列表**排序后与模式 0 逐项比较,并比较完整命中列表,不只是比较数量。
|
||||||
|
|
||||||
|
- 四批完整根列表和命中列表全部一致。
|
||||||
|
- 已知类批次保留原有 24 个命中,其他三批无命中。
|
||||||
|
- 全无根类的全部世界内位置:四条路径都生成 0 个任务、0 个命中;模式 3 正确处理没有具体位置可展开的情况。
|
||||||
|
- 原 21 个压力/边界位置:覆盖原点、世界边界和 int32 边界,四条路径相同的 24 个命中。
|
||||||
|
- 随机打乱 32,768 个混合位置的输入顺序:完整根和结果列表依然一致,位置压缩/还原不依赖同类位置连续排列。
|
||||||
|
|
||||||
|
原始记录在 `pipeline_results/*.jsonl`,汇总、源码哈希在 `pipeline_results/summary.json`;补充检查在 `pipeline_results/edge_validation.json`。
|
||||||
|
|
||||||
|
## 对全世界估算的影响
|
||||||
|
|
||||||
|
本轮证明此前“复用低位根”的前提可以落地,且筛类后只展开有根位置能控制部分开销。但不能把本轮 14.72× 再乘到此前约 50 天的估算上:此前约 50 天已经假定根充分复用,主要计算的是后续搜索。
|
||||||
|
|
||||||
|
本轮仅抽取 512 个随机类,根分布样本不足以重新估计全世界平均耗时;32 个有根类更是有意选择的压力集。维持此前约 50 天作为普通搜索阶段的乐观吞吐量级,不将其升级为完整全世界工期承诺。
|
||||||
|
|
||||||
|
下一步若继续研究算法,应优先减少剩余中间位组的筛选工作,或研究不同实际位置之间能否共享更高位的必要条件。当前低位根复用只消除了根生成重复,不能推导出高位结果也相同。
|
||||||
|
|
||||||
|
## 复现
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 research/experiments/normal_search/run_pipeline.py
|
||||||
|
python3 research/experiments/normal_search/verify_pipeline_edges.py
|
||||||
|
```
|
||||||
|
|
||||||
|
两个脚本都会编译 GPU 基准程序。需要 NVIDIA GPU、nvcc 和 g++ 环境。实现入口为 `pipeline_bench.cu`,复用上一轮 `cuda_bench.cu` 中的内核。原正式入口未替换。
|
||||||
87
research/experiments/normal_search/REPORT.md
Normal file
87
research/experiments/normal_search/REPORT.md
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# 普通搜索:16 个中间位候选成组筛选
|
||||||
|
|
||||||
|
2026-10-11;RTX 3060 Laptop GPU;CUDA sm_86。仅研究普通首次输出分支,未修改或测试补扫。
|
||||||
|
|
||||||
|
结论:保留公共项预计算和 16 候选成组筛选。相对上一轮最优的“输出差过滤+区间求交”,两个独立的 65,536 随机坐标批次,搜索阶段分别加速 6.51×、6.56×。本轮代码在独立实验入口,未替换正式程序。
|
||||||
|
|
||||||
|
## 同任务实测
|
||||||
|
|
||||||
|
预热后轮换模式顺序,七轮中位数。模式 4~7 每轮重新执行 GPU 预计算,耗时已包括预计算 kernel。所有模式使用相同完整低位根列表,完整输出列表逐项比较。
|
||||||
|
|
||||||
|
| 输入 | 旧最优(模式 3) | 32 位预计算(4) | 成组筛选(7) | 3/7 |
|
||||||
|
|---|---:|---:|---:|---:|
|
||||||
|
| 已知命中位置 | 0.323584 ms | 0.268288 ms | 0.156672 ms | 2.07× |
|
||||||
|
| 21 压力/边界位置 | 1.288192 ms | 0.926720 ms | 0.223232 ms | 5.77× |
|
||||||
|
| 4096 随机位置 | 8.857600 ms | 5.859328 ms | 1.141760 ms | 7.76× |
|
||||||
|
| 65536 随机位置,原批次 | 130.136063 ms | 98.654205 ms | 19.982336 ms | 6.51× |
|
||||||
|
| 65536 随机位置,独立新批次 | 134.184967 ms | 102.582275 ms | 20.440063 ms | 6.56× |
|
||||||
|
|
||||||
|
另外测试了半差模 5(模式 5),以及半差模 5 配合角落优先检查(模式 6)。均未优于模式 7,不作为最终方案。原批次模式 5/6 分别为 114.548737/108.295166 ms。
|
||||||
|
|
||||||
|
这是搜索阶段的 CUDA event 时间,**不包括低位根生成、初始化、显存分配、排序及传输**。同次运行原批次根生成 kernel 为 267.922424 ms;若每个坐标仍单独生成根,则根生成加搜索的 kernel 时间之和从 398.058487 ms 降到 287.904760 ms,仅 1.38×。这个和是阶段计时相加,不是完整应用耗时。
|
||||||
|
|
||||||
|
此前已经验证坐标类之间可以复用低位根,但本实验没有实现全世界分组调度;不可把 6.5× 写成现有完整程序的加速倍数。
|
||||||
|
|
||||||
|
## 公共项预计算
|
||||||
|
|
||||||
|
使用归一化种子,令首格坐标项为 0。固定低 18 位 `root`,中间 17 位记为 `m`:
|
||||||
|
|
||||||
|
```
|
||||||
|
a_i = root + term_i
|
||||||
|
h_i = a_i >> 18
|
||||||
|
l_i = (a_i XOR C) & (2^18 - 1)
|
||||||
|
b_i = (A*l_i + 11) >> 17
|
||||||
|
c = C >> 18 = 0x1790d
|
||||||
|
q_i(m) = [b_i + 2*A*((m+h_i) XOR c)] mod 2^31
|
||||||
|
```
|
||||||
|
|
||||||
|
`A=0x5DEECE66D`,`C=0x5E434E432`。`h_i,b_i` 对同一个根的全部 131,072 个 m 共用,每根准备一次。最后公式按 uint32 运算取模即可,无需每个候选重复 64 位乘法。
|
||||||
|
|
||||||
|
## 为什么可以 16 个一起筛
|
||||||
|
|
||||||
|
`c=0x1790d` 的第 4~7 位为 0(最低位编号为 0)。将全部 m 唯一划分为:
|
||||||
|
|
||||||
|
```
|
||||||
|
base = (g >> 4)*256 + (g & 15), g=0..8191
|
||||||
|
m = base + 16*j, j=0..15
|
||||||
|
```
|
||||||
|
|
||||||
|
每组仅变化 m 的第 4~7 位:
|
||||||
|
|
||||||
|
- 首格 term 为 0,因此 `(m XOR c)` 随 j 线性增加,不会跨 256 边界。
|
||||||
|
- 对任意其他格,`m+h_i` 在这段长度 240 的范围内最多跨一次 256 边界。
|
||||||
|
- 同一段内,两格的输出都有相同线性增量。因此 `(q_i-q_0) mod 2^31` 是常数;整组最多两段。
|
||||||
|
- 只需计算首尾两处差值,并由进位位置求分界,即可准确判断 16 个候选哪些满足 `delta % 10 ∈ {0,8}`。
|
||||||
|
|
||||||
|
用 16 位 mask 表示一组的候选,逐格与允许 mask 求交,变成 0 就整组退出。最终剩余的具体 m 才进入原有高位区间求交及精确 Java 验证。
|
||||||
|
|
||||||
|
每根筛选线程从 131,072 个减少到 8,192 个。仍覆盖所有 m,每个线程所做的工作有所增加,因此实际收益不是理论 16×。这也没有消除中间位维度,只是利用局部等价批量处理。
|
||||||
|
|
||||||
|
## 正确性验证
|
||||||
|
|
||||||
|
- 5,000,000 次随机完整输出对照:预计算公式和原 48 位状态公式一致。
|
||||||
|
- 200,000 个随机组,约束数量循环覆盖 1~25:成组 mask 和逐个检查 16 个候选的差过滤完全一致。包括 159,120 个通过的候选,避免只对照空集。
|
||||||
|
- 已知位置全部 84 个低位根 × 8192 组,与原逐候选差过滤完全一致,共覆盖 11,010,048 个中间位候选,保留 36 个差过滤候选。
|
||||||
|
- 以上共验证 888,128 个完整组 mask。
|
||||||
|
- 五组 GPU 基准,全部模式预热与七轮测量均逐项比较完整结果列表;已知位置与压力批次保留原有 24 个命中;随机批次均无命中。
|
||||||
|
- 每批最多 64 个坐标的完整低位根列表与 CPU 对照;压力集包括世界边界、int32 边界及原点。
|
||||||
|
|
||||||
|
普通分支原有的拒绝重抽覆盖范围未改变。本实验不声称单独覆盖所有 Java 重试解。
|
||||||
|
|
||||||
|
## 全世界量级与下一步
|
||||||
|
|
||||||
|
若沿用之前的乐观假设:低位根充分复用、初始化充分摊薄,只用本次搜索阶段吞吐外推 `3,750,000²` 个锚点,则两批分别约 **49.6 天、50.8 天**,相比之前约 0.9 年有明显改善。这不是完整全世界运行时间,不含全世界分组、根缓存展开及传输,也不含补扫;不能据此承诺两个月完成。
|
||||||
|
|
||||||
|
接下来普通流程优先项:实际接上低位坐标类复用调度,计入分组、候选展开、传输的总成本。随后再研究是否能合并更多中间位组,而不是继续仅压缩高位求解的少量幸存者。
|
||||||
|
|
||||||
|
## 复现
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 research/experiments/normal_search/run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
需要可访问 NVIDIA GPU、nvcc、g++。脚本编译程序、运行 CPU 验证、生成独立随机坐标、顺序运行五组 GPU 基准,并写入 `results/`。不会调用补扫 kernel。
|
||||||
|
|
||||||
|
实现:`grouped.hpp`(预计算和组 mask)、`cuda_bench.cu`(基准与分组 kernel)、`cpu_verify.cpp`(独立对照)。原始七轮数据在 `results/*.jsonl`,汇总与源码哈希在 `results/summary.json`。
|
||||||
|
|
||||||
|
后续已完成计入分组、展开及传输的坐标类复用实验,见 [完整批次流程报告](PIPELINE_REPORT.md)。
|
||||||
51
research/experiments/normal_search/cpu_verify.cpp
Normal file
51
research/experiments/normal_search/cpu_verify.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#include "grouped.hpp"
|
||||||
|
#include <random>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
namespace sr=slime_research;
|
||||||
|
namespace ns=normal_search;
|
||||||
|
int main(){
|
||||||
|
std::mt19937_64 rng(2026101103);
|
||||||
|
uint64_t qchecks=0,groups=0,partialSurvivors=0;
|
||||||
|
for(int test=0;test<200000;++test){
|
||||||
|
sr::CellTerm cells[25];
|
||||||
|
int32_t x=int32_t(rng()%3750000)-1875000,z=int32_t(rng()%3750000)-1875000;
|
||||||
|
const uint64_t anchor=sr::coordinateTerm(x,z);
|
||||||
|
for(int i=0;i<25;++i)cells[i]={(sr::coordinateTerm(x+i%5,z+i/5)-anchor)&sr::kMask48,sr::kSlime};
|
||||||
|
uint32_t root=rng()&((1U<<18)-1);
|
||||||
|
// Full-width random middle values also test carry and modulo behavior.
|
||||||
|
uint32_t m=rng()&((1U<<17)-1);
|
||||||
|
ns::Prepared p;ns::prepare(root,cells,p);
|
||||||
|
for(int i=0;i<25;++i){
|
||||||
|
uint32_t expected=sr::nextStateFromTerm(root+(uint64_t(m)<<18),cells[i].term)>>17;
|
||||||
|
if(ns::preparedQ(p,i,m)!=expected)throw std::runtime_error("q mismatch");
|
||||||
|
++qchecks;
|
||||||
|
}
|
||||||
|
// groupMask matches the difference filter even on odd roots; final
|
||||||
|
// solver parity checks remain responsible for discarding those roots.
|
||||||
|
int count=1+test%25;
|
||||||
|
uint32_t base=m&~240U,expected=0;
|
||||||
|
for(uint32_t j=0;j<16;++j)
|
||||||
|
if(full_search_experiment::differenceFilter(root+(uint64_t(base+16*j)<<18),cells,count))expected|=1U<<j;
|
||||||
|
partialSurvivors+=__builtin_popcount(expected);
|
||||||
|
if(ns::groupMask(p,base,count)!=expected)throw std::runtime_error("group mask mismatch");
|
||||||
|
++groups;
|
||||||
|
}
|
||||||
|
// Positive low18 roots and all middle values: check complete filter masks.
|
||||||
|
sr::CellTerm cells[25];const uint64_t anchor=sr::coordinateTerm(51069,419124);
|
||||||
|
for(int i=0;i<25;++i)cells[i]={(sr::coordinateTerm(51069+i%5,419124+i/5)-anchor)&sr::kMask48,sr::kSlime};
|
||||||
|
uint64_t accepted=0;
|
||||||
|
for(uint32_t root=0;root<(1U<<18);++root){
|
||||||
|
if(!sr::partialPatternPossible(root,18,cells,25))continue;
|
||||||
|
ns::Prepared p;ns::prepare(root,cells,p);
|
||||||
|
for(uint32_t g=0;g<8192;++g){
|
||||||
|
uint32_t base=((g>>4)<<8)+(g&15),expected=0;
|
||||||
|
for(uint32_t j=0;j<16;++j)if(full_search_experiment::differenceFilter(root+(uint64_t(base+16*j)<<18),cells,25))expected|=1U<<j;
|
||||||
|
const uint32_t actual=ns::groupMask(p,base);
|
||||||
|
if(actual!=expected)throw std::runtime_error("positive group mask mismatch");
|
||||||
|
accepted+=__builtin_popcount(actual);++groups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!accepted)throw std::runtime_error("no positive candidates tested");
|
||||||
|
std::cout<<"{\"q_checks\":"<<qchecks<<",\"group_masks\":"<<groups<<",\"partial_filter_survivors\":"<<partialSurvivors<<",\"known_filter_survivors\":"<<accepted<<",\"passed\":true}\n";
|
||||||
|
}
|
||||||
215
research/experiments/normal_search/cuda_bench.cu
Normal file
215
research/experiments/normal_search/cuda_bench.cu
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
#include "grouped.hpp"
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <numeric>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr = slime_research;
|
||||||
|
namespace fs = full_search_experiment;
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
#define CUDA(call) do { auto e = (call); if (e != cudaSuccess) throw std::runtime_error(std::string(#call)+": "+cudaGetErrorString(e)); } while (0)
|
||||||
|
struct Position { int32_t x, z; uint64_t anchor; sr::CellTerm cells[25]; };
|
||||||
|
struct Job { uint32_t position, root; };
|
||||||
|
struct Match { uint64_t position, seed; bool operator<(const Match& o)const{return std::tie(position,seed)<std::tie(o.position,o.seed);} bool operator==(const Match& o)const{return position==o.position&&seed==o.seed;} };
|
||||||
|
constexpr uint32_t MATCH_CAPACITY = 1U << 20;
|
||||||
|
constexpr uint32_t JOB_CAPACITY = 1U << 22;
|
||||||
|
|
||||||
|
Position makePosition(int32_t x,int32_t z) {
|
||||||
|
Position p{}; p.x=x;p.z=z;p.anchor=sr::coordinateTerm(x,z);
|
||||||
|
for(int j=0;j<5;++j)for(int i=0;i<5;++i)
|
||||||
|
p.cells[j*5+i]={(sr::coordinateTerm(x+i,z+j)-p.anchor)&sr::kMask48,sr::kSlime};
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int Mode>
|
||||||
|
__global__ void rootKernel(const Position* positions,uint32_t count,const uint32_t* good,
|
||||||
|
uint32_t* counts,Job* jobs,unsigned long long* total,bool collect) {
|
||||||
|
uint64_t idx=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;
|
||||||
|
if(idx >= (uint64_t(count)<<18)) return;
|
||||||
|
const uint32_t pos=uint32_t(idx>>18), root=uint32_t(idx)&((1U<<18)-1);
|
||||||
|
const Position& p=positions[pos];
|
||||||
|
for(int i=0;i<25;++i){
|
||||||
|
const uint32_t in=root+uint32_t(p.cells[i].term);
|
||||||
|
if constexpr(Mode==0){
|
||||||
|
if((((in^uint32_t(sr::kCombinedXor))*uint32_t(sr::kJavaMultiplier)+11U)>>17)&1U)return;
|
||||||
|
}else{
|
||||||
|
const uint32_t k=in&((1U<<18)-1);
|
||||||
|
if(((good[k>>5]>>(k&31))&1U)==0)return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atomicAdd(counts+pos,1U);
|
||||||
|
const auto at=atomicAdd(total,1ULL);
|
||||||
|
if(collect&&at<JOB_CAPACITY)jobs[at]={pos,root};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Emitter {
|
||||||
|
uint32_t position; uint64_t anchor; Match* out; unsigned long long* count;
|
||||||
|
__device__ void operator()(uint64_t s){
|
||||||
|
auto at=atomicAdd(count,1ULL);
|
||||||
|
if(at<MATCH_CAPACITY)out[at]={position,(s-anchor)&sr::kMask48};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using normal_search::Prepared;
|
||||||
|
using normal_search::preparedQ;
|
||||||
|
__global__ void prepareKernel(const Position* positions,const Job* jobs,uint32_t count,Prepared* out){
|
||||||
|
const uint32_t j=blockIdx.x*blockDim.x+threadIdx.x;
|
||||||
|
if(j<count)normal_search::prepare(jobs[j].root,positions[jobs[j].position].cells,out[j]);
|
||||||
|
}
|
||||||
|
template<int Mode>
|
||||||
|
__global__ void searchKernel(const Position* positions,const Job* jobs,uint32_t count,
|
||||||
|
Match* out,unsigned long long* total,const Prepared* prepared) {
|
||||||
|
uint64_t idx=uint64_t(blockIdx.x)*blockDim.x+threadIdx.x;
|
||||||
|
constexpr int bits=Mode==7?13:17;
|
||||||
|
if(idx >= (uint64_t(count)<<bits))return;
|
||||||
|
const Job job=jobs[idx>>bits];
|
||||||
|
const uint32_t m=idx&((1U<<bits)-1);
|
||||||
|
uint64_t low=job.root+(uint64_t(m)<<18);
|
||||||
|
const Position& p=positions[job.position];
|
||||||
|
Emitter emit{job.position,p.anchor,out,total};
|
||||||
|
if constexpr(Mode==7){
|
||||||
|
const uint32_t base=((m>>4)<<8)+(m&15);
|
||||||
|
uint32_t mask=normal_search::groupMask(prepared[idx>>13],base);
|
||||||
|
while(mask){
|
||||||
|
const uint32_t j=__ffs(mask)-1;mask &= mask-1;
|
||||||
|
const uint64_t candidate=job.root+(uint64_t(base+16*j)<<18);
|
||||||
|
fs::solveIntervals(candidate,p.cells,25,emit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr(Mode==3){if(fs::differenceFilter(low,p.cells,25))fs::solveIntervals(low,p.cells,25,emit);}
|
||||||
|
else {
|
||||||
|
const Prepared& t=prepared[idx>>17];
|
||||||
|
const uint32_t q0=preparedQ(t,0,m);
|
||||||
|
const uint32_t u0=(m+t.h[0])^uint32_t(sr::kCombinedXor>>18);
|
||||||
|
const int order[24]={24,4,20,12,2,10,14,22,6,8,16,18,1,3,5,9,15,19,21,23,7,11,13,17};
|
||||||
|
for(int k=1;k<25;++k){
|
||||||
|
const int i=Mode==6?order[k-1]:k;
|
||||||
|
if constexpr(Mode==4){
|
||||||
|
const uint32_t r=((preparedQ(t,i,m)-q0)&0x7fffffffU)%10;
|
||||||
|
if(r!=0&&r!=8)return;
|
||||||
|
}else{
|
||||||
|
const uint32_t ui=(m+t.h[i])^uint32_t(sr::kCombinedXor>>18);
|
||||||
|
const uint32_t d=((t.b[i]>>1)-(t.b[0]>>1)+uint32_t(sr::kJavaMultiplier)*(ui-u0))&((1U<<30)-1);
|
||||||
|
const uint32_t r=d%5;
|
||||||
|
if(r!=0&&r!=4)return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs::solveIntervals(low,p.cells,25,emit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct Device {
|
||||||
|
Prepared* prepared=nullptr; Position* positions=nullptr; Job* jobs=nullptr; Match* matches=nullptr;
|
||||||
|
uint32_t* counts=nullptr;uint32_t* good=nullptr;unsigned long long* total=nullptr;
|
||||||
|
cudaEvent_t start,stop;
|
||||||
|
Device(const std::vector<Position>& p){
|
||||||
|
CUDA(cudaMalloc(&positions,p.size()*sizeof(Position)));CUDA(cudaMemcpy(positions,p.data(),p.size()*sizeof(Position),cudaMemcpyHostToDevice));
|
||||||
|
CUDA(cudaMalloc(&jobs,JOB_CAPACITY*sizeof(Job)));CUDA(cudaMalloc(&matches,MATCH_CAPACITY*sizeof(Match)));
|
||||||
|
CUDA(cudaMalloc(&counts,p.size()*sizeof(uint32_t)));CUDA(cudaMalloc(&total,sizeof(*total)));
|
||||||
|
std::array<uint32_t,8192> bits{};
|
||||||
|
for(uint32_t k=0;k<(1U<<18);++k)if(!((((k^uint32_t(sr::kCombinedXor))*uint32_t(sr::kJavaMultiplier)+11U)>>17)&1U))bits[k>>5]|=1U<<(k&31);
|
||||||
|
CUDA(cudaMalloc(&good,sizeof(bits)));CUDA(cudaMemcpy(good,bits.data(),sizeof(bits),cudaMemcpyHostToDevice));
|
||||||
|
CUDA(cudaEventCreate(&start));CUDA(cudaEventCreate(&stop));
|
||||||
|
}
|
||||||
|
~Device(){cudaFree(prepared);cudaFree(positions);cudaFree(jobs);cudaFree(matches);cudaFree(counts);cudaFree(total);cudaFree(good);cudaEventDestroy(start);cudaEventDestroy(stop);}
|
||||||
|
float finish(){CUDA(cudaGetLastError());CUDA(cudaEventRecord(stop));CUDA(cudaEventSynchronize(stop));float ms;CUDA(cudaEventElapsedTime(&ms,start,stop));return ms;}
|
||||||
|
uint64_t totalCount(){unsigned long long n;CUDA(cudaMemcpy(&n,total,sizeof(n),cudaMemcpyDeviceToHost));return n;}
|
||||||
|
float roots(uint32_t n,int mode,bool collect){
|
||||||
|
CUDA(cudaMemset(total,0,sizeof(*total)));CUDA(cudaMemset(counts,0,n*sizeof(uint32_t)));CUDA(cudaEventRecord(start));
|
||||||
|
unsigned blocks=unsigned(((uint64_t(n)<<18)+255)/256);
|
||||||
|
if(mode==0)rootKernel<0><<<blocks,256>>>(positions,n,good,counts,jobs,total,collect);
|
||||||
|
else rootKernel<1><<<blocks,256>>>(positions,n,good,counts,jobs,total,collect);
|
||||||
|
return finish();
|
||||||
|
}
|
||||||
|
float search(uint32_t n,int mode){
|
||||||
|
if(!prepared&&n)CUDA(cudaMalloc(&prepared,n*sizeof(Prepared)));
|
||||||
|
CUDA(cudaMemset(total,0,sizeof(*total)));CUDA(cudaEventRecord(start));
|
||||||
|
unsigned blocks=unsigned(((uint64_t(n)<<17)+255)/256);
|
||||||
|
if(blocks){
|
||||||
|
if(mode>=4)prepareKernel<<<(n+127)/128,128>>>(positions,jobs,n,prepared);
|
||||||
|
if(mode==3)searchKernel<3><<<blocks,256>>>(positions,jobs,n,matches,total,prepared);
|
||||||
|
if(mode==4)searchKernel<4><<<blocks,256>>>(positions,jobs,n,matches,total,prepared);
|
||||||
|
if(mode==5)searchKernel<5><<<blocks,256>>>(positions,jobs,n,matches,total,prepared);
|
||||||
|
if(mode==6)searchKernel<6><<<blocks,256>>>(positions,jobs,n,matches,total,prepared);
|
||||||
|
if(mode==7)searchKernel<7><<<unsigned(((uint64_t(n)<<13)+255)/256),256>>>(positions,jobs,n,matches,total,prepared);
|
||||||
|
}
|
||||||
|
return finish();
|
||||||
|
}
|
||||||
|
std::vector<Match> results(){
|
||||||
|
uint64_t n=totalCount();if(n>MATCH_CAPACITY)throw std::runtime_error("match overflow");
|
||||||
|
std::vector<Match> r(n);CUDA(cudaMemcpy(r.data(),matches,n*sizeof(Match),cudaMemcpyDeviceToHost));std::sort(r.begin(),r.end());return r;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc,char**argv){
|
||||||
|
try{
|
||||||
|
if(argc<3)throw std::runtime_error("usage: cuda_bench positions.txt normal [repeats=7]");
|
||||||
|
std::ifstream in(argv[1]);if(!in)throw std::runtime_error("cannot open positions");
|
||||||
|
int32_t x,z;std::vector<Position> positions;while(in>>x>>z)positions.push_back(makePosition(x,z));
|
||||||
|
if(positions.empty()||positions.size()>65536)throw std::runtime_error("position count must be 1..65536");
|
||||||
|
std::string task=argv[2];int repeats=argc>3?std::stoi(argv[3]):5;
|
||||||
|
if(task!="normal"||repeats<1)throw std::runtime_error("expected normal and positive repeat count");
|
||||||
|
auto processStart=Clock::now();Device d(positions);
|
||||||
|
std::cout<<std::setprecision(10);
|
||||||
|
cudaDeviceProp prop{};CUDA(cudaGetDeviceProperties(&prop,0));std::cout<<"{\"type\":\"device\",\"name\":\""<<prop.name<<"\",\"positions\":"<<positions.size()<<"}"<<std::endl;
|
||||||
|
std::vector<uint32_t> referenceCounts;
|
||||||
|
std::vector<Job> referenceJobs;
|
||||||
|
auto collectRoots=[&](int mode){
|
||||||
|
float ms=d.roots(positions.size(),mode,true);uint64_t total=d.totalCount();if(total>JOB_CAPACITY)throw std::runtime_error("job overflow");
|
||||||
|
std::vector<uint32_t> counts(positions.size());CUDA(cudaMemcpy(counts.data(),d.counts,counts.size()*sizeof(uint32_t),cudaMemcpyDeviceToHost));
|
||||||
|
std::vector<Job> jobs(total);CUDA(cudaMemcpy(jobs.data(),d.jobs,total*sizeof(Job),cudaMemcpyDeviceToHost));
|
||||||
|
std::sort(jobs.begin(),jobs.end(),[](Job a,Job b){return std::tie(a.position,a.root)<std::tie(b.position,b.root);});
|
||||||
|
if(referenceCounts.empty()){referenceCounts=counts;referenceJobs=jobs;}
|
||||||
|
else {
|
||||||
|
if(counts!=referenceCounts||jobs.size()!=referenceJobs.size())throw std::runtime_error("root mismatch");
|
||||||
|
for(size_t i=0;i<jobs.size();++i)if(jobs[i].position!=referenceJobs[i].position||jobs[i].root!=referenceJobs[i].root)throw std::runtime_error("root list mismatch");
|
||||||
|
}
|
||||||
|
return ms;
|
||||||
|
};
|
||||||
|
{
|
||||||
|
float rootMs=collectRoots(0);
|
||||||
|
CUDA(cudaMemcpy(d.jobs,referenceJobs.data(),referenceJobs.size()*sizeof(Job),cudaMemcpyHostToDevice));
|
||||||
|
std::vector<Match> expected;
|
||||||
|
const std::vector<int> modes=std::vector<int>{3,4,5,6,7};
|
||||||
|
bool first=true;
|
||||||
|
for(int m:modes){d.search(referenceJobs.size(),m);auto r=d.results();if(first){expected=r;first=false;}else if(r!=expected)throw std::runtime_error("warmup result mismatch");}
|
||||||
|
for(int r=0;r<repeats;++r)for(size_t k=0;k<modes.size();++k){
|
||||||
|
int mode=modes[(k+r)%modes.size()];auto wallStart=Clock::now();float ms=d.search(referenceJobs.size(),mode);auto result=d.results();
|
||||||
|
if(result!=expected)throw std::runtime_error("search result mismatch");
|
||||||
|
std::cout<<"{\"type\":\"search_timing\",\"mode\":"<<mode<<",\"round\":"<<r<<",\"kernel_ms\":"<<ms<<",\"scan_ms\":"<<std::chrono::duration<double,std::milli>(Clock::now()-wallStart).count()<<",\"matches\":"<<result.size()<<"}"<<std::endl;
|
||||||
|
}
|
||||||
|
std::cout<<"{\"type\":\"search_info\",\"root_ms\":"<<rootMs<<",\"jobs\":"<<referenceJobs.size()<<",\"lower35\":"<<(uint64_t(referenceJobs.size())<<17)<<",\"matches\":[";
|
||||||
|
for(size_t i=0;i<expected.size();++i){if(i)std::cout<<",";const auto& p=positions[expected[i].position];std::cout<<"["<<expected[i].seed<<","<<p.x<<","<<p.z<<"]";}std::cout<<"]}"<<std::endl;
|
||||||
|
}
|
||||||
|
// CPU checks of complete low18 root lists, including varied coordinates.
|
||||||
|
for(size_t n=0;n<std::min<size_t>(64,positions.size());++n){
|
||||||
|
size_t pos=n*positions.size()/std::min<size_t>(64,positions.size());std::vector<uint32_t> cpu,gpu;
|
||||||
|
for(uint32_t s=0;s<(1U<<18);++s)if(sr::partialPatternPossible(s,18,positions[pos].cells,25))cpu.push_back(s);
|
||||||
|
for(auto j:referenceJobs)if(j.position==pos)gpu.push_back(j.root);
|
||||||
|
if(cpu!=gpu)throw std::runtime_error("CPU/GPU roots mismatch");
|
||||||
|
}
|
||||||
|
std::map<uint64_t,std::vector<uint32_t>> classes;
|
||||||
|
size_t offset=0;
|
||||||
|
for(size_t pos=0;pos<positions.size();++pos){
|
||||||
|
uint64_t key=(uint64_t(uint32_t(positions[pos].x)&65535)<<17)|(uint32_t(positions[pos].z)&131071);
|
||||||
|
std::vector<uint32_t> roots;
|
||||||
|
while(offset<referenceJobs.size()&&referenceJobs[offset].position==pos)roots.push_back(referenceJobs[offset++].root);
|
||||||
|
auto it=classes.find(key);
|
||||||
|
if(it==classes.end())classes.emplace(key,std::move(roots));
|
||||||
|
else if(it->second!=roots)throw std::runtime_error("periodic class root list mismatch");
|
||||||
|
}
|
||||||
|
std::cout<<"{\"type\":\"periodic_validation\",\"classes\":"<<classes.size()<<",\"positions\":"<<positions.size()<<",\"equal\":true}"<<std::endl;
|
||||||
|
std::cout<<"{\"type\":\"root_counts\",\"counts\":[";for(size_t i=0;i<referenceCounts.size();++i){if(i)std::cout<<",";std::cout<<referenceCounts[i];}std::cout<<"]}"<<std::endl;
|
||||||
|
std::cout<<"{\"type\":\"process\",\"ms\":"<<std::chrono::duration<double,std::milli>(Clock::now()-processStart).count()<<"}"<<std::endl;
|
||||||
|
return 0;
|
||||||
|
}catch(const std::exception&e){std::cerr<<e.what()<<"\n";return 1;}
|
||||||
|
}
|
||||||
65536
research/experiments/normal_search/fresh65536.txt
Normal file
65536
research/experiments/normal_search/fresh65536.txt
Normal file
File diff suppressed because it is too large
Load diff
42
research/experiments/normal_search/grouped.hpp
Normal file
42
research/experiments/normal_search/grouped.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
#include "../full_search_feasibility/interval_solver.hpp"
|
||||||
|
#if defined(__CUDACC__)
|
||||||
|
#define NS_HD __host__ __device__ __forceinline__
|
||||||
|
#else
|
||||||
|
#define NS_HD inline
|
||||||
|
#endif
|
||||||
|
namespace normal_search {
|
||||||
|
namespace sr=slime_research;
|
||||||
|
struct Prepared { uint32_t h[25], b[25]; };
|
||||||
|
NS_HD void prepare(uint32_t root,const sr::CellTerm* cells,Prepared& out){
|
||||||
|
for(int i=0;i<25;++i){
|
||||||
|
const uint64_t a=root+cells[i].term;
|
||||||
|
out.h[i]=uint32_t(a>>18);
|
||||||
|
const uint64_t lo=(a^sr::kCombinedXor)&((1U<<18)-1);
|
||||||
|
out.b[i]=uint32_t((lo*sr::kJavaMultiplier+11)>>17);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NS_HD uint32_t preparedQ(const Prepared& p,int i,uint32_t m){
|
||||||
|
const uint32_t u=(m+p.h[i])^uint32_t(sr::kCombinedXor>>18);
|
||||||
|
return (p.b[i]+2U*uint32_t(sr::kJavaMultiplier)*u)&0x7fffffffU;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bits 4..7 of kCombinedXor >> 18 are zero. For m=base+16*j,
|
||||||
|
// j in [0,16), m XOR C is affine. Each shifted input crosses at
|
||||||
|
// most one 256 boundary, so each output difference has at most two values.
|
||||||
|
// base bits 4..7 must be zero; cells[0].term must be zero (normalized anchor).
|
||||||
|
NS_HD uint32_t groupMask(const Prepared& p,uint32_t base,int count=25){
|
||||||
|
uint32_t mask=65535;
|
||||||
|
const uint32_t q0=preparedQ(p,0,base),qend=preparedQ(p,0,base+240);
|
||||||
|
for(int i=1;i<count&&mask;++i){
|
||||||
|
const uint32_t n=(256-((base+p.h[i])&255)+15)>>4;
|
||||||
|
const uint32_t before=(1U<<n)-1;
|
||||||
|
const uint32_t r0=((preparedQ(p,i,base)-q0)&0x7fffffffU)%10;
|
||||||
|
const uint32_t r1=((preparedQ(p,i,base+240)-qend)&0x7fffffffU)%10;
|
||||||
|
const uint32_t allowed=((r0==0||r0==8)?before:0U)|((r1==0||r1==8)?(65535U^before):0U);
|
||||||
|
mask &= allowed;
|
||||||
|
}
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef NS_HD
|
||||||
114
research/experiments/normal_search/pipeline_bench.cu
Normal file
114
research/experiments/normal_search/pipeline_bench.cu
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
#define main component_benchmark_main
|
||||||
|
#include "cuda_bench.cu"
|
||||||
|
#undef main
|
||||||
|
|
||||||
|
__global__ void expandJobs(const uint32_t* positionClass,const uint32_t* offsets,
|
||||||
|
const uint32_t* classOffsets,const uint32_t* roots,
|
||||||
|
uint32_t n,Job* jobs){
|
||||||
|
uint32_t p=blockIdx.x*blockDim.x+threadIdx.x;
|
||||||
|
if(p>=n)return;
|
||||||
|
const uint32_t c=positionClass[p];
|
||||||
|
for(uint32_t i=classOffsets[c];i<classOffsets[c+1];++i)
|
||||||
|
jobs[offsets[p]+i-classOffsets[c]]={p,roots[i]};
|
||||||
|
}
|
||||||
|
struct Extra {
|
||||||
|
Position* reps;uint32_t *positionClass,*offsets,*classOffsets,*roots;
|
||||||
|
Extra(size_t n){
|
||||||
|
CUDA(cudaMalloc(&reps,n*sizeof(Position)));
|
||||||
|
CUDA(cudaMalloc(&positionClass,n*sizeof(uint32_t)));
|
||||||
|
CUDA(cudaMalloc(&offsets,(n+1)*sizeof(uint32_t)));
|
||||||
|
CUDA(cudaMalloc(&classOffsets,(n+1)*sizeof(uint32_t)));
|
||||||
|
CUDA(cudaMalloc(&roots,JOB_CAPACITY*sizeof(uint32_t)));
|
||||||
|
}
|
||||||
|
~Extra(){cudaFree(reps);cudaFree(positionClass);cudaFree(offsets);cudaFree(classOffsets);cudaFree(roots);}
|
||||||
|
};
|
||||||
|
struct Run {double hostMs,buildMs,rootMs,expandMs,searchMs;uint32_t classes,jobs,activePositions;std::vector<Match> matches;std::vector<Job> rootList;};
|
||||||
|
Run run(const std::vector<std::pair<int32_t,int32_t>>& coords,Device& d,Extra& extra,int mode){
|
||||||
|
const auto start=Clock::now();
|
||||||
|
auto ms=[](auto t){return std::chrono::duration<double,std::milli>(Clock::now()-t).count();};
|
||||||
|
std::vector<Position> positions;positions.reserve(coords.size());
|
||||||
|
if(mode!=3)for(auto [x,z]:coords)positions.push_back(makePosition(x,z));
|
||||||
|
std::vector<Position> reps;std::vector<uint32_t> pc;
|
||||||
|
if(mode>=2){
|
||||||
|
std::map<uint64_t,uint32_t> map;
|
||||||
|
for(auto [x,z]:coords){
|
||||||
|
uint64_t key=(uint64_t(uint32_t(x)&65535)<<17)|(uint32_t(z)&131071);
|
||||||
|
auto [it,inserted]=map.emplace(key,uint32_t(reps.size()));
|
||||||
|
if(inserted)reps.push_back(makePosition(x,z));
|
||||||
|
pc.push_back(it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!positions.empty())CUDA(cudaMemcpy(d.positions,positions.data(),positions.size()*sizeof(Position),cudaMemcpyHostToDevice));
|
||||||
|
Run r{};r.classes=mode>=2?reps.size():positions.size();r.buildMs=ms(start);
|
||||||
|
const auto rootStart=Clock::now();
|
||||||
|
if(mode>=2){
|
||||||
|
CUDA(cudaMemcpy(extra.reps,reps.data(),reps.size()*sizeof(Position),cudaMemcpyHostToDevice));
|
||||||
|
CUDA(cudaMemset(d.total,0,sizeof(*d.total)));CUDA(cudaMemset(d.counts,0,reps.size()*sizeof(uint32_t)));
|
||||||
|
rootKernel<0><<<unsigned(((uint64_t(reps.size())<<18)+255)/256),256>>>(extra.reps,reps.size(),nullptr,d.counts,d.jobs,d.total,true);
|
||||||
|
}else d.roots(positions.size(),0,true);
|
||||||
|
uint64_t n=d.totalCount();if(n>JOB_CAPACITY)throw std::runtime_error("root capacity exceeded");
|
||||||
|
r.rootMs=ms(rootStart);
|
||||||
|
const auto expandStart=Clock::now();
|
||||||
|
std::vector<uint32_t> originalIndices;
|
||||||
|
if(mode>=2){
|
||||||
|
std::vector<Job> cj(n);if(n)CUDA(cudaMemcpy(cj.data(),d.jobs,n*sizeof(Job),cudaMemcpyDeviceToHost));
|
||||||
|
std::vector<std::vector<uint32_t>> lists(reps.size());
|
||||||
|
for(auto j:cj)lists[j.position].push_back(j.root);
|
||||||
|
if(mode==3){
|
||||||
|
std::vector<uint32_t> compactClasses;
|
||||||
|
for(uint32_t i=0;i<coords.size();++i)if(!lists[pc[i]].empty()){
|
||||||
|
auto [x,z]=coords[i];positions.push_back(makePosition(x,z));
|
||||||
|
compactClasses.push_back(pc[i]);originalIndices.push_back(i);
|
||||||
|
}
|
||||||
|
pc=std::move(compactClasses);
|
||||||
|
if(!positions.empty())CUDA(cudaMemcpy(d.positions,positions.data(),positions.size()*sizeof(Position),cudaMemcpyHostToDevice));
|
||||||
|
}
|
||||||
|
std::vector<uint32_t> roots,co{0},offsets{0};
|
||||||
|
for(auto& list:lists){std::sort(list.begin(),list.end());roots.insert(roots.end(),list.begin(),list.end());co.push_back(roots.size());}
|
||||||
|
uint64_t expanded=0;
|
||||||
|
for(auto c:pc){expanded+=lists[c].size();if(expanded>JOB_CAPACITY)throw std::runtime_error("expanded capacity exceeded; use smaller batch");offsets.push_back(expanded);}
|
||||||
|
n=expanded;
|
||||||
|
CUDA(cudaMemcpy(extra.positionClass,pc.data(),pc.size()*sizeof(uint32_t),cudaMemcpyHostToDevice));
|
||||||
|
CUDA(cudaMemcpy(extra.offsets,offsets.data(),offsets.size()*sizeof(uint32_t),cudaMemcpyHostToDevice));
|
||||||
|
CUDA(cudaMemcpy(extra.classOffsets,co.data(),co.size()*sizeof(uint32_t),cudaMemcpyHostToDevice));
|
||||||
|
if(!roots.empty())CUDA(cudaMemcpy(extra.roots,roots.data(),roots.size()*sizeof(uint32_t),cudaMemcpyHostToDevice));
|
||||||
|
if(!positions.empty())expandJobs<<<unsigned((positions.size()+255)/256),256>>>(extra.positionClass,extra.offsets,extra.classOffsets,extra.roots,positions.size(),d.jobs);
|
||||||
|
CUDA(cudaGetLastError());CUDA(cudaDeviceSynchronize());
|
||||||
|
}
|
||||||
|
r.expandMs=ms(expandStart);r.jobs=n;r.activePositions=positions.size();
|
||||||
|
const auto searchStart=Clock::now();
|
||||||
|
d.search(n,mode==0?3:7);r.matches=d.results();
|
||||||
|
if(mode==3)for(auto& match:r.matches)match.position=originalIndices[match.position];
|
||||||
|
r.searchMs=ms(searchStart);r.hostMs=ms(start);
|
||||||
|
// Validation-only transfers/sorting are outside reported pipeline time.
|
||||||
|
r.rootList.resize(n);if(n)CUDA(cudaMemcpy(r.rootList.data(),d.jobs,n*sizeof(Job),cudaMemcpyDeviceToHost));
|
||||||
|
if(mode==3)for(auto& job:r.rootList)job.position=originalIndices[job.position];
|
||||||
|
std::sort(r.rootList.begin(),r.rootList.end(),[](Job a,Job b){return std::tie(a.position,a.root)<std::tie(b.position,b.root);});
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
int main(int argc,char**argv){try{
|
||||||
|
if(argc<2)throw std::runtime_error("usage: pipeline_bench positions.txt [repeats=5]");
|
||||||
|
const auto process=Clock::now();
|
||||||
|
std::ifstream in(argv[1]);if(!in)throw std::runtime_error("cannot open input");
|
||||||
|
std::vector<std::pair<int32_t,int32_t>> coords;int32_t x,z;while(in>>x>>z)coords.emplace_back(x,z);
|
||||||
|
if(coords.empty()||coords.size()>65536)throw std::runtime_error("expected 1..65536 coordinates");
|
||||||
|
int repeats=argc>2?std::stoi(argv[2]):5;if(repeats<1)throw std::runtime_error("invalid repeats");
|
||||||
|
Device d(std::vector<Position>(coords.size()));Extra e(coords.size());
|
||||||
|
std::cout<<std::setprecision(10);
|
||||||
|
std::cout<<"{\"type\":\"setup\",\"host_ms\":"<<std::chrono::duration<double,std::milli>(Clock::now()-process).count()<<"}"<<std::endl;
|
||||||
|
Run ref;
|
||||||
|
for(int round=-1;round<repeats;++round)for(int k=0;k<4;++k){
|
||||||
|
int mode=round<0?k:(round+k)%4;
|
||||||
|
Run r=run(coords,d,e,mode);
|
||||||
|
if(round==-1&&mode==0)ref=r;
|
||||||
|
else {
|
||||||
|
if(r.matches!=ref.matches||r.rootList.size()!=ref.rootList.size())throw std::runtime_error("pipeline mismatch");
|
||||||
|
for(size_t i=0;i<r.rootList.size();++i)if(r.rootList[i].position!=ref.rootList[i].position||r.rootList[i].root!=ref.rootList[i].root)throw std::runtime_error("complete root list mismatch");
|
||||||
|
}
|
||||||
|
if(round>=0)std::cout<<"{\"type\":\"pipeline\",\"mode\":"<<mode<<",\"round\":"<<round<<",\"positions\":"<<coords.size()<<",\"classes\":"<<r.classes<<",\"active_positions\":"<<r.activePositions<<",\"jobs\":"<<r.jobs<<",\"matches\":"<<r.matches.size()<<",\"host_ms\":"<<r.hostMs<<",\"build_ms\":"<<r.buildMs<<",\"root_ms\":"<<r.rootMs<<",\"expand_ms\":"<<r.expandMs<<",\"search_ms\":"<<r.searchMs<<"}"<<std::endl;
|
||||||
|
}
|
||||||
|
std::cout<<"{\"type\":\"validation\",\"complete_roots_equal\":true,\"complete_matches_equal\":true,\"matches\":[";
|
||||||
|
for(size_t i=0;i<ref.matches.size();++i){if(i)std::cout<<",";auto m=ref.matches[i];std::cout<<"["<<m.seed<<","<<coords[m.position].first<<","<<coords[m.position].second<<"]";}
|
||||||
|
std::cout<<"]}"<<std::endl;
|
||||||
|
return 0;
|
||||||
|
}catch(const std::exception& e){std::cerr<<e.what()<<"\n";return 1;}}
|
||||||
File diff suppressed because it is too large
Load diff
32768
research/experiments/normal_search/pipeline_positions/mixed512.txt
Normal file
32768
research/experiments/normal_search/pipeline_positions/mixed512.txt
Normal file
File diff suppressed because it is too large
Load diff
32768
research/experiments/normal_search/pipeline_positions/shuffled_mixed.txt
Normal file
32768
research/experiments/normal_search/pipeline_positions/shuffled_mixed.txt
Normal file
File diff suppressed because it is too large
Load diff
1653
research/experiments/normal_search/pipeline_positions/zero_lifts.txt
Normal file
1653
research/experiments/normal_search/pipeline_positions/zero_lifts.txt
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,339 @@
|
||||||
|
{
|
||||||
|
"zero_lifts": {
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"modes": [
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 0,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 1653,
|
||||||
|
"classes": 1653,
|
||||||
|
"active_positions": 1653,
|
||||||
|
"jobs": 0,
|
||||||
|
"matches": 0,
|
||||||
|
"host_ms": 5.571692,
|
||||||
|
"build_ms": 0.090188,
|
||||||
|
"root_ms": 5.379603,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 0.101716
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 1,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 1653,
|
||||||
|
"classes": 1653,
|
||||||
|
"active_positions": 1653,
|
||||||
|
"jobs": 0,
|
||||||
|
"matches": 0,
|
||||||
|
"host_ms": 5.729661,
|
||||||
|
"build_ms": 0.1019,
|
||||||
|
"root_ms": 5.497826,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 0.12975
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 2,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 1653,
|
||||||
|
"classes": 1,
|
||||||
|
"active_positions": 1653,
|
||||||
|
"jobs": 0,
|
||||||
|
"matches": 0,
|
||||||
|
"host_ms": 0.56059,
|
||||||
|
"build_ms": 0.10771,
|
||||||
|
"root_ms": 0.251016,
|
||||||
|
"expand_ms": 0.131594,
|
||||||
|
"search_ms": 0.070177
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 3,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 1653,
|
||||||
|
"classes": 1,
|
||||||
|
"active_positions": 0,
|
||||||
|
"jobs": 0,
|
||||||
|
"matches": 0,
|
||||||
|
"host_ms": 0.230821,
|
||||||
|
"build_ms": 0.006363,
|
||||||
|
"root_ms": 0.083272,
|
||||||
|
"expand_ms": 0.071838,
|
||||||
|
"search_ms": 0.069348
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": [
|
||||||
|
[
|
||||||
|
10358701894766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15959335054446,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18914276748398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
24514909908078,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27469851602030,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33070484761710,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36025426455662,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41626059615342,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44581001309294,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50181634468974,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53136576162926,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
58737209322606,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61692151016558,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67292784176238,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
70247725870190,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75848359029870,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78803300723822,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84403933883502,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87358875577454,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
92959508737134,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
95914450431086,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
101515083590766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
104470025284718,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
110070658444398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modes": [
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 0,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 21,
|
||||||
|
"classes": 21,
|
||||||
|
"active_positions": 21,
|
||||||
|
"jobs": 474,
|
||||||
|
"matches": 24,
|
||||||
|
"host_ms": 1.476311,
|
||||||
|
"build_ms": 0.003135,
|
||||||
|
"root_ms": 0.17549,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 1.297501
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 1,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 21,
|
||||||
|
"classes": 21,
|
||||||
|
"active_positions": 21,
|
||||||
|
"jobs": 474,
|
||||||
|
"matches": 24,
|
||||||
|
"host_ms": 0.505168,
|
||||||
|
"build_ms": 0.004611,
|
||||||
|
"root_ms": 0.175213,
|
||||||
|
"expand_ms": 9.3e-05,
|
||||||
|
"search_ms": 0.325159
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 2,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 21,
|
||||||
|
"classes": 20,
|
||||||
|
"active_positions": 21,
|
||||||
|
"jobs": 474,
|
||||||
|
"matches": 24,
|
||||||
|
"host_ms": 0.834753,
|
||||||
|
"build_ms": 0.011158,
|
||||||
|
"root_ms": 0.242071,
|
||||||
|
"expand_ms": 0.210348,
|
||||||
|
"search_ms": 0.370991
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 3,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 21,
|
||||||
|
"classes": 20,
|
||||||
|
"active_positions": 13,
|
||||||
|
"jobs": 474,
|
||||||
|
"matches": 24,
|
||||||
|
"host_ms": 0.782097,
|
||||||
|
"build_ms": 0.004335,
|
||||||
|
"root_ms": 0.1967,
|
||||||
|
"expand_ms": 0.261529,
|
||||||
|
"search_ms": 0.319533
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shuffled_mixed": {
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"modes": [
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 0,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 32768,
|
||||||
|
"classes": 32768,
|
||||||
|
"active_positions": 32768,
|
||||||
|
"jobs": 15424,
|
||||||
|
"matches": 0,
|
||||||
|
"host_ms": 152.132767,
|
||||||
|
"build_ms": 4.096027,
|
||||||
|
"root_ms": 113.387564,
|
||||||
|
"expand_ms": 0.000184,
|
||||||
|
"search_ms": 34.648623
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 1,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 32768,
|
||||||
|
"classes": 32768,
|
||||||
|
"active_positions": 32768,
|
||||||
|
"jobs": 15424,
|
||||||
|
"matches": 0,
|
||||||
|
"host_ms": 124.087013,
|
||||||
|
"build_ms": 4.152926,
|
||||||
|
"root_ms": 113.998689,
|
||||||
|
"expand_ms": 0.000184,
|
||||||
|
"search_ms": 5.934844
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 2,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 32768,
|
||||||
|
"classes": 512,
|
||||||
|
"active_positions": 32768,
|
||||||
|
"jobs": 15424,
|
||||||
|
"matches": 0,
|
||||||
|
"host_ms": 13.605316,
|
||||||
|
"build_ms": 5.613835,
|
||||||
|
"root_ms": 1.992268,
|
||||||
|
"expand_ms": 0.604117,
|
||||||
|
"search_ms": 5.394911
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pipeline",
|
||||||
|
"mode": 3,
|
||||||
|
"round": 0,
|
||||||
|
"positions": 32768,
|
||||||
|
"classes": 512,
|
||||||
|
"active_positions": 1216,
|
||||||
|
"jobs": 15424,
|
||||||
|
"matches": 0,
|
||||||
|
"host_ms": 9.609161,
|
||||||
|
"build_ms": 1.342779,
|
||||||
|
"root_ms": 2.238581,
|
||||||
|
"expand_ms": 0.603472,
|
||||||
|
"search_ms": 5.424052
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"source_sha256": {
|
||||||
|
"pipeline_bench.cu": "31da815aa0ee8904c702e55d2d685282fc64bfada24dd208e151faac5c55c37c",
|
||||||
|
"cuda_bench.cu": "3e1fd6fe3bdb87ac68fe853dc4e6d2275d6aa00a692c7d630391fd12c0105c20",
|
||||||
|
"grouped.hpp": "5d48c4a45e7f7b07a362c69b5163f48d44bd45d18e7f0f02b00bda0edaadeb23",
|
||||||
|
"verify_pipeline_edges.py": "97a2b4642e871149519a79c73b7b617dd7a0cc47cd0f0dfe59cc7c8afdf0507c"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,301 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"date": "2026-10-11T10:49:17.203764+08:00",
|
||||||
|
"gpu": "name, driver_version\nNVIDIA GeForce RTX 3060 Laptop GPU, 596.49\n"
|
||||||
|
},
|
||||||
|
"known_lifts": {
|
||||||
|
"0": {
|
||||||
|
"host_ms": 359.494613,
|
||||||
|
"build_ms": 0.100726,
|
||||||
|
"root_ms": 8.470989,
|
||||||
|
"expand_ms": 9.6e-05,
|
||||||
|
"search_ms": 350.821402,
|
||||||
|
"jobs": 138852,
|
||||||
|
"classes": 1653,
|
||||||
|
"positions": 1653,
|
||||||
|
"matches": 24
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"host_ms": 61.726379,
|
||||||
|
"build_ms": 0.127304,
|
||||||
|
"root_ms": 8.947655,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 52.659413,
|
||||||
|
"jobs": 138852,
|
||||||
|
"classes": 1653,
|
||||||
|
"positions": 1653,
|
||||||
|
"matches": 24
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"host_ms": 53.625264,
|
||||||
|
"build_ms": 0.166015,
|
||||||
|
"root_ms": 0.356104,
|
||||||
|
"expand_ms": 0.319126,
|
||||||
|
"search_ms": 52.783635,
|
||||||
|
"jobs": 138852,
|
||||||
|
"classes": 1,
|
||||||
|
"positions": 1653,
|
||||||
|
"matches": 24
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": [
|
||||||
|
[
|
||||||
|
10358701894766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15959335054446,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18914276748398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
24514909908078,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27469851602030,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33070484761710,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36025426455662,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41626059615342,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44581001309294,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50181634468974,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53136576162926,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
58737209322606,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61692151016558,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67292784176238,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
70247725870190,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75848359029870,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78803300723822,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84403933883502,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87358875577454,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
92959508737134,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
95914450431086,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
101515083590766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
104470025284718,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
110070658444398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"setup_ms": 269.594069
|
||||||
|
},
|
||||||
|
"nonempty32": {
|
||||||
|
"0": {
|
||||||
|
"host_ms": 4621.165908,
|
||||||
|
"build_ms": 7.240293,
|
||||||
|
"root_ms": 257.582953,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 4356.341998,
|
||||||
|
"jobs": 1693228,
|
||||||
|
"classes": 52213,
|
||||||
|
"positions": 52213,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"host_ms": 926.774796,
|
||||||
|
"build_ms": 7.307786,
|
||||||
|
"root_ms": 251.583683,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 668.081633,
|
||||||
|
"jobs": 1693228,
|
||||||
|
"classes": 52213,
|
||||||
|
"positions": 52213,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"host_ms": 664.598058,
|
||||||
|
"build_ms": 8.052305,
|
||||||
|
"root_ms": 0.602059,
|
||||||
|
"expand_ms": 1.15488,
|
||||||
|
"search_ms": 654.776953,
|
||||||
|
"jobs": 1693228,
|
||||||
|
"classes": 32,
|
||||||
|
"positions": 52213,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"setup_ms": 281.326323
|
||||||
|
},
|
||||||
|
"mixed512": {
|
||||||
|
"0": {
|
||||||
|
"host_ms": 170.81455,
|
||||||
|
"build_ms": 4.3475,
|
||||||
|
"root_ms": 125.510086,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 41.628402,
|
||||||
|
"jobs": 15424,
|
||||||
|
"classes": 32768,
|
||||||
|
"positions": 32768,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"host_ms": 138.433459,
|
||||||
|
"build_ms": 4.575848,
|
||||||
|
"root_ms": 128.008956,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 5.843008,
|
||||||
|
"jobs": 15424,
|
||||||
|
"classes": 32768,
|
||||||
|
"positions": 32768,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"host_ms": 14.418511,
|
||||||
|
"build_ms": 5.463323,
|
||||||
|
"root_ms": 2.456199,
|
||||||
|
"expand_ms": 0.614328,
|
||||||
|
"search_ms": 5.820053,
|
||||||
|
"jobs": 15424,
|
||||||
|
"classes": 512,
|
||||||
|
"positions": 32768,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"setup_ms": 259.425362
|
||||||
|
},
|
||||||
|
"random4096": {
|
||||||
|
"0": {
|
||||||
|
"host_ms": 22.41199,
|
||||||
|
"build_ms": 0.318132,
|
||||||
|
"root_ms": 15.081804,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 6.895335,
|
||||||
|
"jobs": 2960,
|
||||||
|
"classes": 4096,
|
||||||
|
"positions": 4096,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"host_ms": 16.864992,
|
||||||
|
"build_ms": 0.311931,
|
||||||
|
"root_ms": 15.251468,
|
||||||
|
"expand_ms": 0,
|
||||||
|
"search_ms": 1.256979,
|
||||||
|
"jobs": 2960,
|
||||||
|
"classes": 4096,
|
||||||
|
"positions": 4096,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"host_ms": 19.192994,
|
||||||
|
"build_ms": 1.265494,
|
||||||
|
"root_ms": 15.992881,
|
||||||
|
"expand_ms": 0.767331,
|
||||||
|
"search_ms": 1.202645,
|
||||||
|
"jobs": 2960,
|
||||||
|
"classes": 4096,
|
||||||
|
"positions": 4096,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"setup_ms": 248.445216
|
||||||
|
},
|
||||||
|
"source_sha256": {
|
||||||
|
"pipeline_bench.cu": "249b6119832788d37643591407bc7eeebc086af452fe22cf0c756004a28596b0",
|
||||||
|
"cuda_bench.cu": "f762ea050d7c93c00903928f972e41106e917d37a4caceb79630bacd0f9316e1",
|
||||||
|
"grouped.hpp": "5d48c4a45e7f7b07a362c69b5163f48d44bd45d18e7f0f02b00bda0edaadeb23",
|
||||||
|
"run_pipeline.py": "a1aaea904d5a3a0d341ed1956e15f20af93c42c76205a2b8df108878f445ac59"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{"type":"setup","host_ms":220.229718}
|
||||||
|
{"type":"pipeline","mode":0,"round":0,"positions":1653,"classes":1653,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":351.890247,"build_ms":0.123965,"root_ms":8.648427,"expand_ms":0.000189,"search_ms":343.117004}
|
||||||
|
{"type":"pipeline","mode":1,"round":0,"positions":1653,"classes":1653,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":60.282088,"build_ms":0.106201,"root_ms":8.500842,"expand_ms":0.000189,"search_ms":51.674478}
|
||||||
|
{"type":"pipeline","mode":2,"round":0,"positions":1653,"classes":1,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":54.114753,"build_ms":0.118106,"root_ms":0.32588,"expand_ms":0.297156,"search_ms":53.373222}
|
||||||
|
{"type":"pipeline","mode":3,"round":0,"positions":1653,"classes":1,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":56.8665,"build_ms":0.0101,"root_ms":0.2025,"expand_ms":0.6349,"search_ms":56.0187}
|
||||||
|
{"type":"pipeline","mode":1,"round":1,"positions":1653,"classes":1653,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":63.2884,"build_ms":0.113,"root_ms":9.1249,"expand_ms":0.0003,"search_ms":54.0495}
|
||||||
|
{"type":"pipeline","mode":2,"round":1,"positions":1653,"classes":1,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":55.3725,"build_ms":0.1507,"root_ms":0.4095,"expand_ms":0.2649,"search_ms":54.5471}
|
||||||
|
{"type":"pipeline","mode":3,"round":1,"positions":1653,"classes":1,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":55.7387,"build_ms":0.0109,"root_ms":0.2574,"expand_ms":0.7803,"search_ms":54.6898}
|
||||||
|
{"type":"pipeline","mode":0,"round":1,"positions":1653,"classes":1653,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":374.3942,"build_ms":0.114,"root_ms":8.904,"expand_ms":0.0001,"search_ms":365.3759}
|
||||||
|
{"type":"pipeline","mode":2,"round":2,"positions":1653,"classes":1,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":56.1846,"build_ms":0.133,"root_ms":0.3246,"expand_ms":0.2974,"search_ms":55.4289}
|
||||||
|
{"type":"pipeline","mode":3,"round":2,"positions":1653,"classes":1,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":57.1526,"build_ms":0.0088,"root_ms":0.1615,"expand_ms":0.6781,"search_ms":56.3039}
|
||||||
|
{"type":"pipeline","mode":0,"round":2,"positions":1653,"classes":1653,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":364.022951,"build_ms":0.1147,"root_ms":8.7547,"expand_ms":0.0001,"search_ms":355.153251}
|
||||||
|
{"type":"pipeline","mode":1,"round":2,"positions":1653,"classes":1653,"active_positions":1653,"jobs":138852,"matches":24,"host_ms":62.015757,"build_ms":0.113887,"root_ms":8.742087,"expand_ms":0.000191,"search_ms":53.159211}
|
||||||
|
{"type":"validation","complete_roots_equal":true,"complete_matches_equal":true,"matches":[[10358701894766,51069,419124],[15959335054446,51069,419124],[18914276748398,51069,419124],[24514909908078,51069,419124],[27469851602030,51069,419124],[33070484761710,51069,419124],[36025426455662,51069,419124],[41626059615342,51069,419124],[44581001309294,51069,419124],[50181634468974,51069,419124],[53136576162926,51069,419124],[58737209322606,51069,419124],[61692151016558,51069,419124],[67292784176238,51069,419124],[70247725870190,51069,419124],[75848359029870,51069,419124],[78803300723822,51069,419124],[84403933883502,51069,419124],[87358875577454,51069,419124],[92959508737134,51069,419124],[95914450431086,51069,419124],[101515083590766,51069,419124],[104470025284718,51069,419124],[110070658444398,51069,419124]]}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{"type":"setup","host_ms":242.015308}
|
||||||
|
{"type":"pipeline","mode":0,"round":0,"positions":32768,"classes":32768,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":172.222139,"build_ms":5.020376,"root_ms":125.961985,"expand_ms":0.000192,"search_ms":41.239203}
|
||||||
|
{"type":"pipeline","mode":1,"round":0,"positions":32768,"classes":32768,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":141.138417,"build_ms":4.616912,"root_ms":130.393185,"expand_ms":0.000287,"search_ms":6.127841}
|
||||||
|
{"type":"pipeline","mode":2,"round":0,"positions":32768,"classes":512,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":14.093525,"build_ms":5.009729,"root_ms":2.227974,"expand_ms":0.638962,"search_ms":6.216476}
|
||||||
|
{"type":"pipeline","mode":3,"round":0,"positions":32768,"classes":512,"active_positions":1216,"jobs":15424,"matches":0,"host_ms":9.527453,"build_ms":0.399052,"root_ms":2.04888,"expand_ms":0.821317,"search_ms":6.257916}
|
||||||
|
{"type":"pipeline","mode":1,"round":1,"positions":32768,"classes":32768,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":137.613046,"build_ms":4.711591,"root_ms":126.754716,"expand_ms":9.6e-05,"search_ms":6.145971}
|
||||||
|
{"type":"pipeline","mode":2,"round":1,"positions":32768,"classes":512,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":14.235207,"build_ms":5.358898,"root_ms":2.203034,"expand_ms":0.604141,"search_ms":6.068846}
|
||||||
|
{"type":"pipeline","mode":3,"round":1,"positions":32768,"classes":512,"active_positions":1216,"jobs":15424,"matches":0,"host_ms":9.585776,"build_ms":0.416318,"root_ms":1.860291,"expand_ms":0.534307,"search_ms":6.774668}
|
||||||
|
{"type":"pipeline","mode":0,"round":1,"positions":32768,"classes":32768,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":171.867118,"build_ms":4.452687,"root_ms":126.737258,"expand_ms":0.000288,"search_ms":40.676693}
|
||||||
|
{"type":"pipeline","mode":2,"round":2,"positions":32768,"classes":512,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":15.318882,"build_ms":5.280144,"root_ms":2.701464,"expand_ms":0.670138,"search_ms":6.666944}
|
||||||
|
{"type":"pipeline","mode":3,"round":2,"positions":32768,"classes":512,"active_positions":1216,"jobs":15424,"matches":0,"host_ms":10.647196,"build_ms":0.441931,"root_ms":2.114877,"expand_ms":0.690187,"search_ms":7.400009}
|
||||||
|
{"type":"pipeline","mode":0,"round":2,"positions":32768,"classes":32768,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":174.051349,"build_ms":4.55245,"root_ms":127.973647,"expand_ms":0.000192,"search_ms":41.524773}
|
||||||
|
{"type":"pipeline","mode":1,"round":2,"positions":32768,"classes":32768,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":144.526135,"build_ms":4.499307,"root_ms":133.867236,"expand_ms":0.000192,"search_ms":6.159304}
|
||||||
|
{"type":"validation","complete_roots_equal":true,"complete_matches_equal":true,"matches":[]}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{"type":"setup","host_ms":219.927973}
|
||||||
|
{"type":"pipeline","mode":0,"round":0,"positions":52213,"classes":52213,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":4362.48845,"build_ms":7.490479,"root_ms":252.85323,"expand_ms":0.000286,"search_ms":4102.144074}
|
||||||
|
{"type":"pipeline","mode":1,"round":0,"positions":52213,"classes":52213,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":909.690741,"build_ms":7.012591,"root_ms":242.747725,"expand_ms":0.000183,"search_ms":659.929967}
|
||||||
|
{"type":"pipeline","mode":2,"round":0,"positions":52213,"classes":32,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":636.817316,"build_ms":7.196841,"root_ms":0.554217,"expand_ms":1.022633,"search_ms":628.04335}
|
||||||
|
{"type":"pipeline","mode":3,"round":0,"positions":52213,"classes":32,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":633.461125,"build_ms":0.338158,"root_ms":0.504258,"expand_ms":8.844275,"search_ms":623.77425}
|
||||||
|
{"type":"pipeline","mode":1,"round":1,"positions":52213,"classes":52213,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":915.445942,"build_ms":7.53445,"root_ms":245.545208,"expand_ms":9.2e-05,"search_ms":662.365642}
|
||||||
|
{"type":"pipeline","mode":2,"round":1,"positions":52213,"classes":32,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":656.710025,"build_ms":9.4644,"root_ms":0.576767,"expand_ms":1.500033,"search_ms":645.168458}
|
||||||
|
{"type":"pipeline","mode":3,"round":1,"positions":52213,"classes":32,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":676.742292,"build_ms":0.3428,"root_ms":0.671,"expand_ms":9.1207,"search_ms":666.607392}
|
||||||
|
{"type":"pipeline","mode":0,"round":1,"positions":52213,"classes":52213,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":4691.310731,"build_ms":7.216464,"root_ms":255.097691,"expand_ms":0.000286,"search_ms":4428.995719}
|
||||||
|
{"type":"pipeline","mode":2,"round":2,"positions":52213,"classes":32,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":685.115982,"build_ms":7.652735,"root_ms":0.58709,"expand_ms":1.326998,"search_ms":675.548969}
|
||||||
|
{"type":"pipeline","mode":3,"round":2,"positions":52213,"classes":32,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":681.412155,"build_ms":0.344485,"root_ms":0.473976,"expand_ms":8.571836,"search_ms":672.021573}
|
||||||
|
{"type":"pipeline","mode":0,"round":2,"positions":52213,"classes":52213,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":4816.532778,"build_ms":9.669654,"root_ms":263.964214,"expand_ms":0.000191,"search_ms":4542.898147}
|
||||||
|
{"type":"pipeline","mode":1,"round":2,"positions":52213,"classes":52213,"active_positions":52213,"jobs":1693228,"matches":0,"host_ms":980.983116,"build_ms":7.505432,"root_ms":259.835073,"expand_ms":0.000191,"search_ms":713.641749}
|
||||||
|
{"type":"validation","complete_roots_equal":true,"complete_matches_equal":true,"matches":[]}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{"type":"setup","host_ms":216.555239}
|
||||||
|
{"type":"pipeline","mode":0,"round":0,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":22.564546,"build_ms":0.29315,"root_ms":15.171827,"expand_ms":0.000192,"search_ms":7.099377}
|
||||||
|
{"type":"pipeline","mode":1,"round":0,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":17.388386,"build_ms":0.316459,"root_ms":15.843212,"expand_ms":0.000192,"search_ms":1.228427}
|
||||||
|
{"type":"pipeline","mode":2,"round":0,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":19.225847,"build_ms":1.497691,"root_ms":15.909497,"expand_ms":0.624573,"search_ms":1.193798}
|
||||||
|
{"type":"pipeline","mode":3,"round":0,"positions":4096,"classes":4096,"active_positions":118,"jobs":2960,"matches":0,"host_ms":17.880678,"build_ms":1.123676,"root_ms":14.852298,"expand_ms":0.623134,"search_ms":1.281474}
|
||||||
|
{"type":"pipeline","mode":1,"round":1,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":17.902261,"build_ms":0.291998,"root_ms":16.411382,"expand_ms":0.000287,"search_ms":1.19821}
|
||||||
|
{"type":"pipeline","mode":2,"round":1,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":19.238988,"build_ms":1.595823,"root_ms":15.836689,"expand_ms":0.631288,"search_ms":1.174708}
|
||||||
|
{"type":"pipeline","mode":3,"round":1,"positions":4096,"classes":4096,"active_positions":118,"jobs":2960,"matches":0,"host_ms":21.575071,"build_ms":1.05058,"root_ms":17.458508,"expand_ms":0.716758,"search_ms":2.349129}
|
||||||
|
{"type":"pipeline","mode":0,"round":1,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":33.127423,"build_ms":0.307347,"root_ms":24.924514,"expand_ms":0.000192,"search_ms":7.895082}
|
||||||
|
{"type":"pipeline","mode":2,"round":2,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":20.589721,"build_ms":1.355912,"root_ms":17.315675,"expand_ms":0.630137,"search_ms":1.287517}
|
||||||
|
{"type":"pipeline","mode":3,"round":2,"positions":4096,"classes":4096,"active_positions":118,"jobs":2960,"matches":0,"host_ms":19.501921,"build_ms":1.103531,"root_ms":16.471047,"expand_ms":0.668987,"search_ms":1.257972}
|
||||||
|
{"type":"pipeline","mode":0,"round":2,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":24.022332,"build_ms":0.288545,"root_ms":16.179721,"expand_ms":0.000191,"search_ms":7.553683}
|
||||||
|
{"type":"pipeline","mode":1,"round":2,"positions":4096,"classes":4096,"active_positions":4096,"jobs":2960,"matches":0,"host_ms":18.798496,"build_ms":0.350033,"root_ms":16.509706,"expand_ms":0.000191,"search_ms":1.938279}
|
||||||
|
{"type":"validation","complete_roots_equal":true,"complete_matches_equal":true,"matches":[]}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{"type":"setup","host_ms":200.876289}
|
||||||
|
{"type":"pipeline","mode":0,"round":0,"positions":32768,"classes":32768,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":152.132767,"build_ms":4.096027,"root_ms":113.387564,"expand_ms":0.000184,"search_ms":34.648623}
|
||||||
|
{"type":"pipeline","mode":1,"round":0,"positions":32768,"classes":32768,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":124.087013,"build_ms":4.152926,"root_ms":113.998689,"expand_ms":0.000184,"search_ms":5.934844}
|
||||||
|
{"type":"pipeline","mode":2,"round":0,"positions":32768,"classes":512,"active_positions":32768,"jobs":15424,"matches":0,"host_ms":13.605316,"build_ms":5.613835,"root_ms":1.992268,"expand_ms":0.604117,"search_ms":5.394911}
|
||||||
|
{"type":"pipeline","mode":3,"round":0,"positions":32768,"classes":512,"active_positions":1216,"jobs":15424,"matches":0,"host_ms":9.609161,"build_ms":1.342779,"root_ms":2.238581,"expand_ms":0.603472,"search_ms":5.424052}
|
||||||
|
{"type":"validation","complete_roots_equal":true,"complete_matches_equal":true,"matches":[]}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{"type":"setup","host_ms":175.300766}
|
||||||
|
{"type":"pipeline","mode":0,"round":0,"positions":21,"classes":21,"active_positions":21,"jobs":474,"matches":24,"host_ms":1.476311,"build_ms":0.003135,"root_ms":0.17549,"expand_ms":0,"search_ms":1.297501}
|
||||||
|
{"type":"pipeline","mode":1,"round":0,"positions":21,"classes":21,"active_positions":21,"jobs":474,"matches":24,"host_ms":0.505168,"build_ms":0.004611,"root_ms":0.175213,"expand_ms":9.3e-05,"search_ms":0.325159}
|
||||||
|
{"type":"pipeline","mode":2,"round":0,"positions":21,"classes":20,"active_positions":21,"jobs":474,"matches":24,"host_ms":0.834753,"build_ms":0.011158,"root_ms":0.242071,"expand_ms":0.210348,"search_ms":0.370991}
|
||||||
|
{"type":"pipeline","mode":3,"round":0,"positions":21,"classes":20,"active_positions":13,"jobs":474,"matches":24,"host_ms":0.782097,"build_ms":0.004335,"root_ms":0.1967,"expand_ms":0.261529,"search_ms":0.319533}
|
||||||
|
{"type":"validation","complete_roots_equal":true,"complete_matches_equal":true,"matches":[[10358701894766,51069,419124],[15959335054446,51069,419124],[18914276748398,51069,419124],[24514909908078,51069,419124],[27469851602030,51069,419124],[33070484761710,51069,419124],[36025426455662,51069,419124],[41626059615342,51069,419124],[44581001309294,51069,419124],[50181634468974,51069,419124],[53136576162926,51069,419124],[58737209322606,51069,419124],[61692151016558,51069,419124],[67292784176238,51069,419124],[70247725870190,51069,419124],[75848359029870,51069,419124],[78803300723822,51069,419124],[84403933883502,51069,419124],[87358875577454,51069,419124],[92959508737134,51069,419124],[95914450431086,51069,419124],[101515083590766,51069,419124],[104470025284718,51069,419124],[110070658444398,51069,419124]]}
|
||||||
361
research/experiments/normal_search/pipeline_results/summary.json
Normal file
361
research/experiments/normal_search/pipeline_results/summary.json
Normal file
|
|
@ -0,0 +1,361 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"date": "2026-10-11T10:51:13.411084+08:00",
|
||||||
|
"gpu": "name, driver_version\nNVIDIA GeForce RTX 3060 Laptop GPU, 596.49\n"
|
||||||
|
},
|
||||||
|
"known_lifts": {
|
||||||
|
"0": {
|
||||||
|
"host_ms": 364.022951,
|
||||||
|
"build_ms": 0.1147,
|
||||||
|
"root_ms": 8.7547,
|
||||||
|
"expand_ms": 0.0001,
|
||||||
|
"search_ms": 355.153251,
|
||||||
|
"jobs": 138852,
|
||||||
|
"classes": 1653,
|
||||||
|
"positions": 1653,
|
||||||
|
"active_positions": 1653,
|
||||||
|
"matches": 24
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"host_ms": 62.015757,
|
||||||
|
"build_ms": 0.113,
|
||||||
|
"root_ms": 8.742087,
|
||||||
|
"expand_ms": 0.000191,
|
||||||
|
"search_ms": 53.159211,
|
||||||
|
"jobs": 138852,
|
||||||
|
"classes": 1653,
|
||||||
|
"positions": 1653,
|
||||||
|
"active_positions": 1653,
|
||||||
|
"matches": 24
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"host_ms": 55.3725,
|
||||||
|
"build_ms": 0.133,
|
||||||
|
"root_ms": 0.32588,
|
||||||
|
"expand_ms": 0.297156,
|
||||||
|
"search_ms": 54.5471,
|
||||||
|
"jobs": 138852,
|
||||||
|
"classes": 1,
|
||||||
|
"positions": 1653,
|
||||||
|
"active_positions": 1653,
|
||||||
|
"matches": 24
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"host_ms": 56.8665,
|
||||||
|
"build_ms": 0.0101,
|
||||||
|
"root_ms": 0.2025,
|
||||||
|
"expand_ms": 0.6781,
|
||||||
|
"search_ms": 56.0187,
|
||||||
|
"jobs": 138852,
|
||||||
|
"classes": 1,
|
||||||
|
"positions": 1653,
|
||||||
|
"active_positions": 1653,
|
||||||
|
"matches": 24
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": [
|
||||||
|
[
|
||||||
|
10358701894766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15959335054446,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18914276748398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
24514909908078,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27469851602030,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33070484761710,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36025426455662,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41626059615342,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44581001309294,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50181634468974,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53136576162926,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
58737209322606,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61692151016558,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67292784176238,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
70247725870190,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75848359029870,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78803300723822,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84403933883502,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87358875577454,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
92959508737134,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
95914450431086,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
101515083590766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
104470025284718,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
110070658444398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"setup_ms": 220.229718
|
||||||
|
},
|
||||||
|
"nonempty32": {
|
||||||
|
"0": {
|
||||||
|
"host_ms": 4691.310731,
|
||||||
|
"build_ms": 7.490479,
|
||||||
|
"root_ms": 255.097691,
|
||||||
|
"expand_ms": 0.000286,
|
||||||
|
"search_ms": 4428.995719,
|
||||||
|
"jobs": 1693228,
|
||||||
|
"classes": 52213,
|
||||||
|
"positions": 52213,
|
||||||
|
"active_positions": 52213,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"host_ms": 915.445942,
|
||||||
|
"build_ms": 7.505432,
|
||||||
|
"root_ms": 245.545208,
|
||||||
|
"expand_ms": 0.000183,
|
||||||
|
"search_ms": 662.365642,
|
||||||
|
"jobs": 1693228,
|
||||||
|
"classes": 52213,
|
||||||
|
"positions": 52213,
|
||||||
|
"active_positions": 52213,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"host_ms": 656.710025,
|
||||||
|
"build_ms": 7.652735,
|
||||||
|
"root_ms": 0.576767,
|
||||||
|
"expand_ms": 1.326998,
|
||||||
|
"search_ms": 645.168458,
|
||||||
|
"jobs": 1693228,
|
||||||
|
"classes": 32,
|
||||||
|
"positions": 52213,
|
||||||
|
"active_positions": 52213,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"host_ms": 676.742292,
|
||||||
|
"build_ms": 0.3428,
|
||||||
|
"root_ms": 0.504258,
|
||||||
|
"expand_ms": 8.844275,
|
||||||
|
"search_ms": 666.607392,
|
||||||
|
"jobs": 1693228,
|
||||||
|
"classes": 32,
|
||||||
|
"positions": 52213,
|
||||||
|
"active_positions": 52213,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"setup_ms": 219.927973
|
||||||
|
},
|
||||||
|
"mixed512": {
|
||||||
|
"0": {
|
||||||
|
"host_ms": 172.222139,
|
||||||
|
"build_ms": 4.55245,
|
||||||
|
"root_ms": 126.737258,
|
||||||
|
"expand_ms": 0.000192,
|
||||||
|
"search_ms": 41.239203,
|
||||||
|
"jobs": 15424,
|
||||||
|
"classes": 32768,
|
||||||
|
"positions": 32768,
|
||||||
|
"active_positions": 32768,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"host_ms": 141.138417,
|
||||||
|
"build_ms": 4.616912,
|
||||||
|
"root_ms": 130.393185,
|
||||||
|
"expand_ms": 0.000192,
|
||||||
|
"search_ms": 6.145971,
|
||||||
|
"jobs": 15424,
|
||||||
|
"classes": 32768,
|
||||||
|
"positions": 32768,
|
||||||
|
"active_positions": 32768,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"host_ms": 14.235207,
|
||||||
|
"build_ms": 5.280144,
|
||||||
|
"root_ms": 2.227974,
|
||||||
|
"expand_ms": 0.638962,
|
||||||
|
"search_ms": 6.216476,
|
||||||
|
"jobs": 15424,
|
||||||
|
"classes": 512,
|
||||||
|
"positions": 32768,
|
||||||
|
"active_positions": 32768,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"host_ms": 9.585776,
|
||||||
|
"build_ms": 0.416318,
|
||||||
|
"root_ms": 2.04888,
|
||||||
|
"expand_ms": 0.690187,
|
||||||
|
"search_ms": 6.774668,
|
||||||
|
"jobs": 15424,
|
||||||
|
"classes": 512,
|
||||||
|
"positions": 32768,
|
||||||
|
"active_positions": 1216,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"setup_ms": 242.015308
|
||||||
|
},
|
||||||
|
"random4096": {
|
||||||
|
"0": {
|
||||||
|
"host_ms": 24.022332,
|
||||||
|
"build_ms": 0.29315,
|
||||||
|
"root_ms": 16.179721,
|
||||||
|
"expand_ms": 0.000192,
|
||||||
|
"search_ms": 7.553683,
|
||||||
|
"jobs": 2960,
|
||||||
|
"classes": 4096,
|
||||||
|
"positions": 4096,
|
||||||
|
"active_positions": 4096,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"host_ms": 17.902261,
|
||||||
|
"build_ms": 0.316459,
|
||||||
|
"root_ms": 16.411382,
|
||||||
|
"expand_ms": 0.000192,
|
||||||
|
"search_ms": 1.228427,
|
||||||
|
"jobs": 2960,
|
||||||
|
"classes": 4096,
|
||||||
|
"positions": 4096,
|
||||||
|
"active_positions": 4096,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"host_ms": 19.238988,
|
||||||
|
"build_ms": 1.497691,
|
||||||
|
"root_ms": 15.909497,
|
||||||
|
"expand_ms": 0.630137,
|
||||||
|
"search_ms": 1.193798,
|
||||||
|
"jobs": 2960,
|
||||||
|
"classes": 4096,
|
||||||
|
"positions": 4096,
|
||||||
|
"active_positions": 4096,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"host_ms": 19.501921,
|
||||||
|
"build_ms": 1.103531,
|
||||||
|
"root_ms": 16.471047,
|
||||||
|
"expand_ms": 0.668987,
|
||||||
|
"search_ms": 1.281474,
|
||||||
|
"jobs": 2960,
|
||||||
|
"classes": 4096,
|
||||||
|
"positions": 4096,
|
||||||
|
"active_positions": 118,
|
||||||
|
"matches": 0
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"type": "validation",
|
||||||
|
"complete_roots_equal": true,
|
||||||
|
"complete_matches_equal": true,
|
||||||
|
"matches": []
|
||||||
|
},
|
||||||
|
"setup_ms": 216.555239
|
||||||
|
},
|
||||||
|
"source_sha256": {
|
||||||
|
"pipeline_bench.cu": "31da815aa0ee8904c702e55d2d685282fc64bfada24dd208e151faac5c55c37c",
|
||||||
|
"cuda_bench.cu": "3e1fd6fe3bdb87ac68fe853dc4e6d2275d6aa00a692c7d630391fd12c0105c20",
|
||||||
|
"grouped.hpp": "5d48c4a45e7f7b07a362c69b5163f48d44bd45d18e7f0f02b00bda0edaadeb23",
|
||||||
|
"run_pipeline.py": "ce3f9e01fad8978541c581233bfeaa4023acade24bbc736f479be6ac14e88591"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
{"type":"setup","host_ms":197.591869}
|
||||||
|
{"type":"pipeline","mode":0,"round":0,"positions":1653,"classes":1653,"active_positions":1653,"jobs":0,"matches":0,"host_ms":5.571692,"build_ms":0.090188,"root_ms":5.379603,"expand_ms":0,"search_ms":0.101716}
|
||||||
|
{"type":"pipeline","mode":1,"round":0,"positions":1653,"classes":1653,"active_positions":1653,"jobs":0,"matches":0,"host_ms":5.729661,"build_ms":0.1019,"root_ms":5.497826,"expand_ms":0,"search_ms":0.12975}
|
||||||
|
{"type":"pipeline","mode":2,"round":0,"positions":1653,"classes":1,"active_positions":1653,"jobs":0,"matches":0,"host_ms":0.56059,"build_ms":0.10771,"root_ms":0.251016,"expand_ms":0.131594,"search_ms":0.070177}
|
||||||
|
{"type":"pipeline","mode":3,"round":0,"positions":1653,"classes":1,"active_positions":0,"jobs":0,"matches":0,"host_ms":0.230821,"build_ms":0.006363,"root_ms":0.083272,"expand_ms":0.071838,"search_ms":0.069348}
|
||||||
|
{"type":"validation","complete_roots_equal":true,"complete_matches_equal":true,"matches":[]}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"q_checks":5000000,"group_masks":888128,"partial_filter_survivors":159120,"known_filter_survivors":36,"passed":true}
|
||||||
40
research/experiments/normal_search/results/fresh65536.jsonl
Normal file
40
research/experiments/normal_search/results/fresh65536.jsonl
Normal file
File diff suppressed because one or more lines are too long
40
research/experiments/normal_search/results/known.jsonl
Normal file
40
research/experiments/normal_search/results/known.jsonl
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":1}
|
||||||
|
{"type":"search_timing","mode":3,"round":0,"kernel_ms":0.3235839903,"scan_ms":0.46062,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":0,"kernel_ms":0.2693119943,"scan_ms":0.393283,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":0,"kernel_ms":0.2897920012,"scan_ms":0.44485,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":0,"kernel_ms":0.2846719921,"scan_ms":0.431015,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":0,"kernel_ms":0.1566720009,"scan_ms":0.292181,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":1,"kernel_ms":0.2682879865,"scan_ms":0.380609,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":1,"kernel_ms":0.2959359884,"scan_ms":0.421824,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":1,"kernel_ms":0.2887679935,"scan_ms":0.400152,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":1,"kernel_ms":0.1556479931,"scan_ms":0.275249,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":1,"kernel_ms":0.3235839903,"scan_ms":0.449784,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":2,"kernel_ms":0.2938880026,"scan_ms":0.40615,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":2,"kernel_ms":0.2897920012,"scan_ms":0.40199,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":2,"kernel_ms":0.1566720009,"scan_ms":0.269735,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":2,"kernel_ms":0.3225600123,"scan_ms":0.437013,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":2,"kernel_ms":0.2693119943,"scan_ms":0.3779,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":3,"kernel_ms":0.2897920012,"scan_ms":0.411375,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":3,"kernel_ms":0.1556479931,"scan_ms":0.269929,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":3,"kernel_ms":0.3194879889,"scan_ms":0.427629,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":3,"kernel_ms":0.2682879865,"scan_ms":0.375288,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":3,"kernel_ms":0.2969599962,"scan_ms":0.414665,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":4,"kernel_ms":0.1566720009,"scan_ms":0.268187,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":4,"kernel_ms":0.3235839903,"scan_ms":0.426952,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":4,"kernel_ms":0.2682879865,"scan_ms":0.425307,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":4,"kernel_ms":0.2949120104,"scan_ms":0.415051,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":4,"kernel_ms":0.2897920012,"scan_ms":0.407022,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":5,"kernel_ms":0.3235839903,"scan_ms":0.427339,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":5,"kernel_ms":0.2631680071,"scan_ms":0.37432,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":5,"kernel_ms":0.2949120104,"scan_ms":0.403248,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":5,"kernel_ms":0.2846719921,"scan_ms":0.436142,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":5,"kernel_ms":0.1505279988,"scan_ms":0.388832,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":6,"kernel_ms":0.4679679871,"scan_ms":0.896375,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":6,"kernel_ms":0.5027840137,"scan_ms":0.714295,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":6,"kernel_ms":0.4864000082,"scan_ms":0.691172,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":6,"kernel_ms":0.1587200016,"scan_ms":0.454428,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":6,"kernel_ms":0.3205119967,"scan_ms":0.426854,"matches":24}
|
||||||
|
{"type":"search_info","root_ms":1.090559959,"jobs":84,"lower35":11010048,"matches":[[10358701894766,51069,419124],[15959335054446,51069,419124],[18914276748398,51069,419124],[24514909908078,51069,419124],[27469851602030,51069,419124],[33070484761710,51069,419124],[36025426455662,51069,419124],[41626059615342,51069,419124],[44581001309294,51069,419124],[50181634468974,51069,419124],[53136576162926,51069,419124],[58737209322606,51069,419124],[61692151016558,51069,419124],[67292784176238,51069,419124],[70247725870190,51069,419124],[75848359029870,51069,419124],[78803300723822,51069,419124],[84403933883502,51069,419124],[87358875577454,51069,419124],[92959508737134,51069,419124],[95914450431086,51069,419124],[101515083590766,51069,419124],[104470025284718,51069,419124],[110070658444398,51069,419124]]}
|
||||||
|
{"type":"periodic_validation","classes":1,"positions":1,"equal":true}
|
||||||
|
{"type":"root_counts","counts":[84]}
|
||||||
|
{"type":"process","ms":234.661262}
|
||||||
40
research/experiments/normal_search/results/random4096.jsonl
Normal file
40
research/experiments/normal_search/results/random4096.jsonl
Normal file
File diff suppressed because one or more lines are too long
40
research/experiments/normal_search/results/random65536.jsonl
Normal file
40
research/experiments/normal_search/results/random65536.jsonl
Normal file
File diff suppressed because one or more lines are too long
40
research/experiments/normal_search/results/stress.jsonl
Normal file
40
research/experiments/normal_search/results/stress.jsonl
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{"type":"device","name":"NVIDIA GeForce RTX 3060 Laptop GPU","positions":21}
|
||||||
|
{"type":"search_timing","mode":3,"round":0,"kernel_ms":1.288192034,"scan_ms":1.497474,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":0,"kernel_ms":0.9256960154,"scan_ms":1.163402,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":0,"kernel_ms":1.083392024,"scan_ms":1.280467,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":0,"kernel_ms":1.010687947,"scan_ms":1.163015,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":0,"kernel_ms":0.224255994,"scan_ms":0.397249,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":1,"kernel_ms":0.922623992,"scan_ms":1.157113,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":1,"kernel_ms":2.477056026,"scan_ms":2.639978,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":1,"kernel_ms":1.007616043,"scan_ms":1.288401,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":1,"kernel_ms":0.2191359997,"scan_ms":0.471262,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":1,"kernel_ms":1.288192034,"scan_ms":1.444263,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":2,"kernel_ms":1.078271985,"scan_ms":1.256764,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":2,"kernel_ms":1.006592035,"scan_ms":1.21168,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":2,"kernel_ms":0.2232320011,"scan_ms":0.403248,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":2,"kernel_ms":1.288192034,"scan_ms":1.439328,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":2,"kernel_ms":0.9277439713,"scan_ms":1.074297,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":3,"kernel_ms":1.010687947,"scan_ms":1.17298,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":3,"kernel_ms":0.2191359997,"scan_ms":0.391735,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":3,"kernel_ms":1.28512001,"scan_ms":1.408369,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":3,"kernel_ms":0.922623992,"scan_ms":1.240607,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":3,"kernel_ms":1.338368058,"scan_ms":1.72406,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":4,"kernel_ms":0.2273280025,"scan_ms":0.686431,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":4,"kernel_ms":1.284096003,"scan_ms":1.587644,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":4,"kernel_ms":0.9267200232,"scan_ms":1.158467,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":4,"kernel_ms":1.08544004,"scan_ms":1.29943,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":4,"kernel_ms":1.010687947,"scan_ms":1.240704,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":5,"kernel_ms":1.289216042,"scan_ms":1.494572,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":5,"kernel_ms":0.9267200232,"scan_ms":1.150921,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":5,"kernel_ms":1.084416032,"scan_ms":1.289078,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":5,"kernel_ms":1.010687947,"scan_ms":1.221837,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":5,"kernel_ms":0.2201599926,"scan_ms":0.470488,"matches":24}
|
||||||
|
{"type":"search_timing","mode":4,"round":6,"kernel_ms":0.9277439713,"scan_ms":1.136892,"matches":24}
|
||||||
|
{"type":"search_timing","mode":5,"round":6,"kernel_ms":1.083392024,"scan_ms":1.321102,"matches":24}
|
||||||
|
{"type":"search_timing","mode":6,"round":6,"kernel_ms":1.781759977,"scan_ms":1.983442,"matches":24}
|
||||||
|
{"type":"search_timing","mode":7,"round":6,"kernel_ms":0.2252800018,"scan_ms":0.355744,"matches":24}
|
||||||
|
{"type":"search_timing","mode":3,"round":6,"kernel_ms":1.93740797,"scan_ms":2.027463,"matches":24}
|
||||||
|
{"type":"search_info","root_ms":1.161216021,"jobs":474,"lower35":62128128,"matches":[[10358701894766,51069,419124],[15959335054446,51069,419124],[18914276748398,51069,419124],[24514909908078,51069,419124],[27469851602030,51069,419124],[33070484761710,51069,419124],[36025426455662,51069,419124],[41626059615342,51069,419124],[44581001309294,51069,419124],[50181634468974,51069,419124],[53136576162926,51069,419124],[58737209322606,51069,419124],[61692151016558,51069,419124],[67292784176238,51069,419124],[70247725870190,51069,419124],[75848359029870,51069,419124],[78803300723822,51069,419124],[84403933883502,51069,419124],[87358875577454,51069,419124],[92959508737134,51069,419124],[95914450431086,51069,419124],[101515083590766,51069,419124],[104470025284718,51069,419124],[110070658444398,51069,419124]]}
|
||||||
|
{"type":"periodic_validation","classes":20,"positions":21,"equal":true}
|
||||||
|
{"type":"root_counts","counts":[84,152,16,0,2,20,61,22,0,1,35,30,20,29,0,2,0,0,0,0,0]}
|
||||||
|
{"type":"process","ms":276.458389}
|
||||||
331
research/experiments/normal_search/results/summary.json
Normal file
331
research/experiments/normal_search/results/summary.json
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"date": "2026-10-11T09:46:12.120634+08:00",
|
||||||
|
"cpu_validation": {
|
||||||
|
"q_checks": 5000000,
|
||||||
|
"group_masks": 888128,
|
||||||
|
"partial_filter_survivors": 159120,
|
||||||
|
"known_filter_survivors": 36,
|
||||||
|
"passed": true
|
||||||
|
},
|
||||||
|
"gpu": "name, driver_version\nNVIDIA GeForce RTX 3060 Laptop GPU, 596.49\n"
|
||||||
|
},
|
||||||
|
"known": {
|
||||||
|
"3": 0.3235839903,
|
||||||
|
"4": 0.2682879865,
|
||||||
|
"5": 0.2949120104,
|
||||||
|
"6": 0.2897920012,
|
||||||
|
"7": 0.1566720009,
|
||||||
|
"info": {
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 1.090559959,
|
||||||
|
"jobs": 84,
|
||||||
|
"lower35": 11010048,
|
||||||
|
"matches": [
|
||||||
|
[
|
||||||
|
10358701894766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15959335054446,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18914276748398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
24514909908078,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27469851602030,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33070484761710,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36025426455662,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41626059615342,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44581001309294,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50181634468974,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53136576162926,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
58737209322606,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61692151016558,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67292784176238,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
70247725870190,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75848359029870,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78803300723822,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84403933883502,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87358875577454,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
92959508737134,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
95914450431086,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
101515083590766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
104470025284718,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
110070658444398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stress": {
|
||||||
|
"3": 1.288192034,
|
||||||
|
"4": 0.9267200232,
|
||||||
|
"5": 1.084416032,
|
||||||
|
"6": 1.010687947,
|
||||||
|
"7": 0.2232320011,
|
||||||
|
"info": {
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 1.161216021,
|
||||||
|
"jobs": 474,
|
||||||
|
"lower35": 62128128,
|
||||||
|
"matches": [
|
||||||
|
[
|
||||||
|
10358701894766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
15959335054446,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
18914276748398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
24514909908078,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
27469851602030,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
33070484761710,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
36025426455662,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
41626059615342,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
44581001309294,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
50181634468974,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
53136576162926,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
58737209322606,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
61692151016558,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67292784176238,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
70247725870190,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
75848359029870,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
78803300723822,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
84403933883502,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
87358875577454,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
92959508737134,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
95914450431086,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
101515083590766,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
104470025284718,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
],
|
||||||
|
[
|
||||||
|
110070658444398,
|
||||||
|
51069,
|
||||||
|
419124
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"random4096": {
|
||||||
|
"3": 8.857600212,
|
||||||
|
"4": 5.859327793,
|
||||||
|
"5": 6.7450881,
|
||||||
|
"6": 5.53574419,
|
||||||
|
"7": 1.141759992,
|
||||||
|
"info": {
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 19.01977539,
|
||||||
|
"jobs": 2960,
|
||||||
|
"lower35": 387973120,
|
||||||
|
"matches": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"random65536": {
|
||||||
|
"3": 130.1360626,
|
||||||
|
"4": 98.65420532,
|
||||||
|
"5": 114.5487366,
|
||||||
|
"6": 108.295166,
|
||||||
|
"7": 19.98233604,
|
||||||
|
"info": {
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 267.9224243,
|
||||||
|
"jobs": 45356,
|
||||||
|
"lower35": 5944901632,
|
||||||
|
"matches": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fresh65536": {
|
||||||
|
"3": 134.184967,
|
||||||
|
"4": 102.5822754,
|
||||||
|
"5": 118.9703674,
|
||||||
|
"6": 112.1454086,
|
||||||
|
"7": 20.44006348,
|
||||||
|
"info": {
|
||||||
|
"type": "search_info",
|
||||||
|
"root_ms": 277.3555298,
|
||||||
|
"jobs": 46148,
|
||||||
|
"lower35": 6048710656,
|
||||||
|
"matches": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source_sha256": {
|
||||||
|
"cuda_bench.cu": "f762ea050d7c93c00903928f972e41106e917d37a4caceb79630bacd0f9316e1",
|
||||||
|
"grouped.hpp": "5d48c4a45e7f7b07a362c69b5163f48d44bd45d18e7f0f02b00bda0edaadeb23",
|
||||||
|
"cpu_verify.cpp": "2e2b6799bd624b3e0a3022c2144c4a6f665c08de530d8f2846e6bc748ba6412f",
|
||||||
|
"run.py": "daca9d7de3463fd501a99b10d921974b73c206acc6e1438a246ae5729043c15e"
|
||||||
|
}
|
||||||
|
}
|
||||||
28
research/experiments/normal_search/run.py
Normal file
28
research/experiments/normal_search/run.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import subprocess,json,statistics,hashlib,random,datetime
|
||||||
|
from pathlib import Path
|
||||||
|
HERE=Path(__file__).resolve().parent
|
||||||
|
POS=HERE.parent/'full_search_feasibility'/'positions'
|
||||||
|
OUT=HERE/'results'
|
||||||
|
OUT.mkdir(exist_ok=True)
|
||||||
|
subprocess.run(['nvcc','-O3','-std=c++17','-arch=sm_86',str(HERE/'cuda_bench.cu'),'-o','/tmp/slime_normal_cuda'],check=True)
|
||||||
|
subprocess.run(['g++','-O3','-std=c++17','-Wall','-Wextra',str(HERE/'cpu_verify.cpp'),'-o','/tmp/slime_normal_verify'],check=True)
|
||||||
|
validation=subprocess.check_output(['/tmp/slime_normal_verify'],text=True)
|
||||||
|
(OUT/'cpu_verify.json').write_text(validation)
|
||||||
|
rng=random.Random(2026101104)
|
||||||
|
fresh=HERE/'fresh65536.txt'
|
||||||
|
fresh.write_text(''.join(f'{rng.randrange(-1875000,1875000)} {rng.randrange(-1875000,1875000)}\n' for _ in range(65536)))
|
||||||
|
summary={'metadata':{'date':datetime.datetime.now().astimezone().isoformat(),'cpu_validation':json.loads(validation),'gpu':subprocess.check_output(['nvidia-smi','--query-gpu=name,driver_version','--format=csv'],text=True)}}
|
||||||
|
for name in ['known','stress','random4096','random65536','fresh65536']:
|
||||||
|
cmd=['/tmp/slime_normal_cuda',str(fresh if name=='fresh65536' else POS/(name+'.txt')),'normal','7']
|
||||||
|
r=subprocess.run(cmd,capture_output=True,text=True,timeout=300)
|
||||||
|
(OUT/(name+'.jsonl')).write_text(r.stdout)
|
||||||
|
(OUT/(name+'.stderr')).write_text(r.stderr)
|
||||||
|
if r.returncode: raise RuntimeError(r.stderr)
|
||||||
|
rows=[json.loads(s) for s in r.stdout.splitlines()]
|
||||||
|
summary[name]={str(m):statistics.median(v['kernel_ms'] for v in rows if v['type']=='search_timing' and v['mode']==m) for m in [3,4,5,6,7]}
|
||||||
|
summary[name]['info']=next(v for v in rows if v['type']=='search_info')
|
||||||
|
print(name,json.dumps(summary[name]),flush=True)
|
||||||
|
summary['source_sha256']={name:hashlib.sha256((HERE/name).read_bytes()).hexdigest() for name in ['cuda_bench.cu','grouped.hpp','cpu_verify.cpp','run.py']}
|
||||||
|
summary['shared_headers_sha256']={name:hashlib.sha256((HERE.parents[2]/'include'/'slime'/name).read_bytes()).hexdigest() for name in ['solver_core.hpp','upper_interval.hpp']}
|
||||||
|
(OUT/'summary.json').write_text(json.dumps(summary,indent=2))
|
||||||
44
research/experiments/normal_search/run_pipeline.py
Normal file
44
research/experiments/normal_search/run_pipeline.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import subprocess,json,random,statistics,hashlib,datetime
|
||||||
|
from pathlib import Path
|
||||||
|
HERE=Path(__file__).resolve().parent
|
||||||
|
OLD=HERE.parent/'full_search_feasibility'
|
||||||
|
OUT=HERE/'pipeline_results'
|
||||||
|
POS=HERE/'pipeline_positions'
|
||||||
|
OUT.mkdir(exist_ok=True);POS.mkdir(exist_ok=True)
|
||||||
|
def lifts(x,z):
|
||||||
|
return [(xx,zz) for xx in range(-1875000+(x+1875000)%65536,1875000,65536)
|
||||||
|
for zz in range(-1875000+(z+1875000)%131072,1875000,131072)]
|
||||||
|
def save(name,coords):
|
||||||
|
p=POS/(name+'.txt');p.write_text(''.join(f'{x} {z}\n' for x,z in coords));return p
|
||||||
|
known=save('known_lifts',lifts(51069,419124))
|
||||||
|
rng=random.Random(2026101105)
|
||||||
|
coords=[]
|
||||||
|
classes=set()
|
||||||
|
while len(classes)<512:
|
||||||
|
c=(rng.randrange(65536),rng.randrange(131072))
|
||||||
|
if c in classes:continue
|
||||||
|
classes.add(c)
|
||||||
|
coords.extend(rng.sample(lifts(*c),64))
|
||||||
|
mixed=save('mixed512',coords)
|
||||||
|
cases={'known_lifts':known,'nonempty32':OLD/'positions/lifts.txt','mixed512':mixed,'random4096':OLD/'positions/random4096.txt'}
|
||||||
|
subprocess.run(['nvcc','-O3','-std=c++17','-arch=sm_86',str(HERE/'pipeline_bench.cu'),'-o','/tmp/slime_pipeline_cuda'],check=True)
|
||||||
|
summary={'metadata':{'date':datetime.datetime.now().astimezone().isoformat(),'gpu':subprocess.check_output(['nvidia-smi','--query-gpu=name,driver_version','--format=csv'],text=True)}}
|
||||||
|
for name,path in cases.items():
|
||||||
|
cmd=['/tmp/slime_pipeline_cuda',str(path),'3']
|
||||||
|
p=subprocess.run(cmd,capture_output=True,text=True,timeout=300)
|
||||||
|
(OUT/(name+'.jsonl')).write_text(p.stdout);(OUT/(name+'.stderr')).write_text(p.stderr)
|
||||||
|
if p.returncode:raise RuntimeError(p.stderr)
|
||||||
|
rows=[json.loads(l) for l in p.stdout.splitlines()]
|
||||||
|
d={}
|
||||||
|
for m in range(4):
|
||||||
|
a=[r for r in rows if r['type']=='pipeline' and r['mode']==m]
|
||||||
|
d[str(m)]={k:statistics.median(r[k] for r in a) for k in ['host_ms','build_ms','root_ms','expand_ms','search_ms','jobs','classes','positions','active_positions','matches']}
|
||||||
|
d['validation']=next(r for r in rows if r['type']=='validation')
|
||||||
|
d['setup_ms']=next(r['host_ms'] for r in rows if r['type']=='setup')
|
||||||
|
summary[name]=d
|
||||||
|
(OUT/'summary.json').write_text(json.dumps(summary,indent=2))
|
||||||
|
print(name,json.dumps({k:v for k,v in d.items() if k!='validation'}),flush=True)
|
||||||
|
summary['source_sha256']={n:hashlib.sha256((HERE/n).read_bytes()).hexdigest() for n in ['pipeline_bench.cu','cuda_bench.cu','grouped.hpp','run_pipeline.py']}
|
||||||
|
summary['shared_headers_sha256']={name:hashlib.sha256((HERE.parents[2]/'include'/'slime'/name).read_bytes()).hexdigest() for name in ['solver_core.hpp','upper_interval.hpp']}
|
||||||
|
(OUT/'summary.json').write_text(json.dumps(summary,indent=2))
|
||||||
28
research/experiments/normal_search/verify_pipeline_edges.py
Normal file
28
research/experiments/normal_search/verify_pipeline_edges.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import json,subprocess,random,hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
H=Path(__file__).resolve().parent
|
||||||
|
OLD=H.parent/'full_search_feasibility'
|
||||||
|
POS=H/'pipeline_positions';OUT=H/'pipeline_results'
|
||||||
|
rows=[json.loads(l) for l in (OLD/'results/random4096.jsonl').read_text().splitlines()]
|
||||||
|
counts=next(r['counts'] for r in rows if r['type']=='root_counts')
|
||||||
|
points=[tuple(map(int,l.split())) for l in (OLD/'positions/random4096.txt').read_text().splitlines()]
|
||||||
|
x,z=points[counts.index(0)]
|
||||||
|
zero=[(xx,zz) for xx in range(-1875000+(x+1875000)%65536,1875000,65536) for zz in range(-1875000+(z+1875000)%131072,1875000,131072)]
|
||||||
|
p=POS/'zero_lifts.txt';p.write_text(''.join(f'{x} {z}\n' for x,z in zero))
|
||||||
|
shuffled=(POS/'mixed512.txt').read_text().splitlines();random.Random(2026101106).shuffle(shuffled)
|
||||||
|
q=POS/'shuffled_mixed.txt';q.write_text('\n'.join(shuffled)+'\n')
|
||||||
|
subprocess.run(['nvcc','-O3','-std=c++17','-arch=sm_86',str(H/'pipeline_bench.cu'),'-o','/tmp/slime_pipeline_cuda'],check=True)
|
||||||
|
checks={}
|
||||||
|
for name,path in [('zero_lifts',p),('stress',OLD/'positions/stress.txt'),('shuffled_mixed',q)]:
|
||||||
|
r=subprocess.run(['/tmp/slime_pipeline_cuda',str(path),'1'],capture_output=True,text=True,timeout=120)
|
||||||
|
(OUT/(name+'.jsonl')).write_text(r.stdout);(OUT/(name+'.stderr')).write_text(r.stderr)
|
||||||
|
if r.returncode:raise RuntimeError(r.stderr)
|
||||||
|
rows=[json.loads(l) for l in r.stdout.splitlines()]
|
||||||
|
checks[name]={'validation':next(r for r in rows if r['type']=='validation'),'modes':[r for r in rows if r['type']=='pipeline']}
|
||||||
|
if name=='zero_lifts':assert all(r['jobs']==0 and r['matches']==0 for r in checks[name]['modes'])
|
||||||
|
if name=='stress':assert len(checks[name]['validation']['matches'])==24
|
||||||
|
print(name,'passed',flush=True)
|
||||||
|
checks['source_sha256']={n:hashlib.sha256((H/n).read_bytes()).hexdigest() for n in ['pipeline_bench.cu','cuda_bench.cu','grouped.hpp','verify_pipeline_edges.py']}
|
||||||
|
checks['shared_headers_sha256']={name:hashlib.sha256((H.parents[2]/'include'/'slime'/name).read_bytes()).hexdigest() for name in ['solver_core.hpp','upper_interval.hpp']}
|
||||||
|
(OUT/'edge_validation.json').write_text(json.dumps(checks,indent=2))
|
||||||
89
research/slime_bruteforce_cpp_bench.cpp
Normal file
89
research/slime_bruteforce_cpp_bench.cpp
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
#include "slime_solver_core.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr = slime_research;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Args {
|
||||||
|
int32_t x = 51069;
|
||||||
|
int32_t z = 419124;
|
||||||
|
int32_t size = 5;
|
||||||
|
uint64_t start = 0;
|
||||||
|
uint64_t count = 1ULL << 30;
|
||||||
|
int threads = std::max(1U, std::thread::hardware_concurrency());
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* value(int argc, char** argv, int& i) {
|
||||||
|
if (++i >= argc) std::exit(2);
|
||||||
|
return argv[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Args parseArgs(int argc, char** argv) {
|
||||||
|
Args args;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
const std::string arg = argv[i];
|
||||||
|
if (arg == "--x") args.x = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--z") args.z = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "-n") args.size = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--start") args.start = std::stoull(value(argc, argv, i));
|
||||||
|
else if (arg == "--count") args.count = std::stoull(value(argc, argv, i));
|
||||||
|
else if (arg == "-p" || arg == "--threads") args.threads = std::stoi(value(argc, argv, i));
|
||||||
|
else std::exit(2);
|
||||||
|
}
|
||||||
|
if (args.size <= 0 || int64_t(args.size) * args.size > sr::kMaxPatternCells ||
|
||||||
|
args.count == 0 || args.threads <= 0) std::exit(2);
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
const Args args = parseArgs(argc, argv);
|
||||||
|
std::vector<sr::CellTerm> terms;
|
||||||
|
for (int32_t dz = 0; dz < args.size; ++dz) {
|
||||||
|
for (int32_t dx = 0; dx < args.size; ++dx) {
|
||||||
|
terms.push_back({sr::coordinateTerm(args.x + dx, args.z + dz), sr::kSlime});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint64_t batch = 4096;
|
||||||
|
std::atomic<uint64_t> next{0};
|
||||||
|
std::atomic<uint64_t> matches{0};
|
||||||
|
const auto begin = std::chrono::steady_clock::now();
|
||||||
|
auto worker = [&] {
|
||||||
|
uint64_t localMatches = 0;
|
||||||
|
for (;;) {
|
||||||
|
const uint64_t first = next.fetch_add(batch, std::memory_order_relaxed);
|
||||||
|
if (first >= args.count) break;
|
||||||
|
const uint64_t last = std::min(first + batch, args.count);
|
||||||
|
for (uint64_t offset = first; offset < last; ++offset) {
|
||||||
|
if (sr::verifyPattern(
|
||||||
|
(args.start + offset) & sr::kMask48,
|
||||||
|
terms.data(), int(terms.size()))) {
|
||||||
|
++localMatches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matches.fetch_add(localMatches, std::memory_order_relaxed);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::thread> workers;
|
||||||
|
for (int i = 0; i < args.threads; ++i) workers.emplace_back(worker);
|
||||||
|
for (std::thread& workerThread : workers) workerThread.join();
|
||||||
|
const std::chrono::duration<double> elapsed = std::chrono::steady_clock::now() - begin;
|
||||||
|
const double seedsPerSecond = double(args.count) / elapsed.count();
|
||||||
|
const double fullSeconds = double(1ULL << 48) / seedsPerSecond;
|
||||||
|
std::cout << "count=" << args.count << " matches=" << matches.load()
|
||||||
|
<< " seconds=" << elapsed.count() << " seeds_per_second=" << seedsPerSecond
|
||||||
|
<< " projected_full48_seconds=" << fullSeconds << '\n';
|
||||||
|
}
|
||||||
137
research/slime_bruteforce_cuda_bench.cu
Normal file
137
research/slime_bruteforce_cuda_bench.cu
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
#include "slime_solver_core.hpp"
|
||||||
|
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr = slime_research;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#define CUDA_CHECK(call) do { \
|
||||||
|
const cudaError_t error_ = (call); \
|
||||||
|
if (error_ != cudaSuccess) { \
|
||||||
|
std::cerr << #call << ": " << cudaGetErrorString(error_) << '\n'; \
|
||||||
|
std::exit(1); \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
struct Args {
|
||||||
|
int32_t x = 51069;
|
||||||
|
int32_t z = 419124;
|
||||||
|
int32_t size = 5;
|
||||||
|
uint64_t start = 0;
|
||||||
|
uint64_t count = 1ULL << 32;
|
||||||
|
int threads = 256;
|
||||||
|
int blocksPerSm = 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* value(int argc, char** argv, int& i) {
|
||||||
|
if (++i >= argc) std::exit(2);
|
||||||
|
return argv[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Args parseArgs(int argc, char** argv) {
|
||||||
|
Args args;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
const std::string arg = argv[i];
|
||||||
|
if (arg == "--x") args.x = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--z") args.z = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "-n") args.size = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--start") args.start = std::stoull(value(argc, argv, i));
|
||||||
|
else if (arg == "--count") args.count = std::stoull(value(argc, argv, i));
|
||||||
|
else if (arg == "--threads") args.threads = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--blocks-per-sm") args.blocksPerSm = std::stoi(value(argc, argv, i));
|
||||||
|
else {
|
||||||
|
std::cerr << "Usage: slime_bruteforce_cuda_bench [--x X --z Z -n N] "
|
||||||
|
"[--start N --count N --threads N --blocks-per-sm N]\n";
|
||||||
|
std::exit(arg == "--help" ? 0 : 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (args.size <= 0 || int64_t(args.size) * args.size > sr::kMaxPatternCells ||
|
||||||
|
args.count == 0 || args.threads <= 0 || args.threads > 1024 || args.blocksPerSm <= 0) {
|
||||||
|
std::exit(2);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
__global__ void bruteForceKernel(
|
||||||
|
uint64_t start,
|
||||||
|
uint64_t count,
|
||||||
|
const sr::CellTerm* cells,
|
||||||
|
int cellCount,
|
||||||
|
unsigned long long* matches
|
||||||
|
) {
|
||||||
|
const uint64_t stride = uint64_t(gridDim.x) * blockDim.x;
|
||||||
|
for (uint64_t offset = uint64_t(blockIdx.x) * blockDim.x + threadIdx.x;
|
||||||
|
offset < count;
|
||||||
|
offset += stride) {
|
||||||
|
if (sr::verifyPattern((start + offset) & sr::kMask48, cells, cellCount)) {
|
||||||
|
atomicAdd(matches, 1ULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
const Args args = parseArgs(argc, argv);
|
||||||
|
std::vector<sr::CellTerm> terms;
|
||||||
|
for (int32_t dz = 0; dz < args.size; ++dz) {
|
||||||
|
for (int32_t dx = 0; dx < args.size; ++dx) {
|
||||||
|
terms.push_back({sr::coordinateTerm(args.x + dx, args.z + dz), sr::kSlime});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int device = 0;
|
||||||
|
cudaDeviceProp properties{};
|
||||||
|
CUDA_CHECK(cudaGetDevice(&device));
|
||||||
|
CUDA_CHECK(cudaGetDeviceProperties(&properties, device));
|
||||||
|
const int blocks = properties.multiProcessorCount * args.blocksPerSm;
|
||||||
|
|
||||||
|
sr::CellTerm* deviceTerms = nullptr;
|
||||||
|
unsigned long long* deviceMatches = nullptr;
|
||||||
|
CUDA_CHECK(cudaMalloc(&deviceTerms, terms.size() * sizeof(sr::CellTerm)));
|
||||||
|
CUDA_CHECK(cudaMalloc(&deviceMatches, sizeof(unsigned long long)));
|
||||||
|
CUDA_CHECK(cudaMemcpy(deviceTerms, terms.data(), terms.size() * sizeof(sr::CellTerm), cudaMemcpyHostToDevice));
|
||||||
|
CUDA_CHECK(cudaMemset(deviceMatches, 0, sizeof(unsigned long long)));
|
||||||
|
|
||||||
|
// Warm up the context and the exact same kernel before timing.
|
||||||
|
bruteForceKernel<<<blocks, args.threads>>>(args.start, 1U << 20, deviceTerms, int(terms.size()), deviceMatches);
|
||||||
|
CUDA_CHECK(cudaDeviceSynchronize());
|
||||||
|
CUDA_CHECK(cudaMemset(deviceMatches, 0, sizeof(unsigned long long)));
|
||||||
|
|
||||||
|
cudaEvent_t begin;
|
||||||
|
cudaEvent_t end;
|
||||||
|
CUDA_CHECK(cudaEventCreate(&begin));
|
||||||
|
CUDA_CHECK(cudaEventCreate(&end));
|
||||||
|
CUDA_CHECK(cudaEventRecord(begin));
|
||||||
|
bruteForceKernel<<<blocks, args.threads>>>(
|
||||||
|
args.start, args.count, deviceTerms, int(terms.size()), deviceMatches
|
||||||
|
);
|
||||||
|
CUDA_CHECK(cudaGetLastError());
|
||||||
|
CUDA_CHECK(cudaEventRecord(end));
|
||||||
|
CUDA_CHECK(cudaEventSynchronize(end));
|
||||||
|
|
||||||
|
float milliseconds = 0.0F;
|
||||||
|
unsigned long long matches = 0;
|
||||||
|
CUDA_CHECK(cudaEventElapsedTime(&milliseconds, begin, end));
|
||||||
|
CUDA_CHECK(cudaMemcpy(&matches, deviceMatches, sizeof(matches), cudaMemcpyDeviceToHost));
|
||||||
|
const double seconds = milliseconds / 1000.0;
|
||||||
|
const double seedsPerSecond = double(args.count) / seconds;
|
||||||
|
const double fullSeconds = double(1ULL << 48) / seedsPerSecond;
|
||||||
|
std::cout << "gpu=\"" << properties.name << "\" count=" << args.count
|
||||||
|
<< " matches=" << matches << " milliseconds=" << milliseconds
|
||||||
|
<< " seeds_per_second=" << seedsPerSecond
|
||||||
|
<< " projected_full48_seconds=" << fullSeconds << '\n';
|
||||||
|
|
||||||
|
cudaEventDestroy(begin);
|
||||||
|
cudaEventDestroy(end);
|
||||||
|
cudaFree(deviceTerms);
|
||||||
|
cudaFree(deviceMatches);
|
||||||
|
}
|
||||||
237
research/slime_pattern_cpp.cpp
Normal file
237
research/slime_pattern_cpp.cpp
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
#include "slime_solver_core.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr = slime_research;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct CellOffset {
|
||||||
|
int32_t dx;
|
||||||
|
int32_t dz;
|
||||||
|
uint8_t requirement;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Args {
|
||||||
|
std::string command;
|
||||||
|
int32_t x = 0;
|
||||||
|
int32_t z = 0;
|
||||||
|
int32_t size = 5;
|
||||||
|
std::string pattern;
|
||||||
|
uint64_t seed = 0;
|
||||||
|
uint64_t lower35 = 0;
|
||||||
|
int bits = 24;
|
||||||
|
int threads = std::max(1U, std::thread::hardware_concurrency());
|
||||||
|
uint64_t maxResults = 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[noreturn]] void usage(int code = 1) {
|
||||||
|
std::cerr
|
||||||
|
<< "Usage:\n"
|
||||||
|
<< " slime_pattern_cpp verify --seed N --x X --z Z [-n N|--pattern ROW/ROW]\n"
|
||||||
|
<< " slime_pattern_cpp upper --lower35 N --x X --z Z [-n N|--pattern ...]\n"
|
||||||
|
<< " slime_pattern_cpp roots --x X --z Z [--bits 18..35] [-n N|--pattern ...]\n"
|
||||||
|
<< " slime_pattern_cpp search --x X --z Z [-p THREADS] [--max-results N] ...\n";
|
||||||
|
std::exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* value(int argc, char** argv, int& i) {
|
||||||
|
if (++i >= argc) usage();
|
||||||
|
return argv[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Args parseArgs(int argc, char** argv) {
|
||||||
|
if (argc < 2) usage();
|
||||||
|
Args args;
|
||||||
|
args.command = argv[1];
|
||||||
|
for (int i = 2; i < argc; ++i) {
|
||||||
|
const std::string arg = argv[i];
|
||||||
|
if (arg == "--x") args.x = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--z") args.z = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "-n" || arg == "--size") args.size = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--pattern") args.pattern = value(argc, argv, i);
|
||||||
|
else if (arg == "--seed") args.seed = std::stoull(value(argc, argv, i));
|
||||||
|
else if (arg == "--lower35") args.lower35 = std::stoull(value(argc, argv, i));
|
||||||
|
else if (arg == "--bits") args.bits = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "-p" || arg == "--threads") args.threads = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--max-results") args.maxResults = std::stoull(value(argc, argv, i));
|
||||||
|
else if (arg == "--help") usage(0);
|
||||||
|
else usage();
|
||||||
|
}
|
||||||
|
if (args.size <= 0 || args.bits < 18 || args.bits > 35 || args.threads <= 0) usage();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<CellOffset> parsePattern(const Args& args) {
|
||||||
|
std::vector<CellOffset> offsets;
|
||||||
|
if (args.pattern.empty()) {
|
||||||
|
if (int64_t(args.size) * args.size > sr::kMaxPatternCells) {
|
||||||
|
throw std::runtime_error("pattern exceeds the 64-cell prototype limit");
|
||||||
|
}
|
||||||
|
for (int32_t dz = 0; dz < args.size; ++dz) {
|
||||||
|
for (int32_t dx = 0; dx < args.size; ++dx) {
|
||||||
|
offsets.push_back({dx, dz, sr::kSlime});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t dx = 0;
|
||||||
|
int32_t dz = 0;
|
||||||
|
int32_t width = -1;
|
||||||
|
for (size_t i = 0; i <= args.pattern.size(); ++i) {
|
||||||
|
const char c = i == args.pattern.size() ? '/' : args.pattern[i];
|
||||||
|
if (c == '/') {
|
||||||
|
if (dx == 0 || (width >= 0 && dx != width)) {
|
||||||
|
throw std::runtime_error("pattern rows must have equal non-zero width");
|
||||||
|
}
|
||||||
|
width = dx;
|
||||||
|
dx = 0;
|
||||||
|
++dz;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '#') offsets.push_back({dx, dz, sr::kSlime});
|
||||||
|
else if (c == '.') offsets.push_back({dx, dz, sr::kNonSlime});
|
||||||
|
else if (c != '?') throw std::runtime_error("pattern accepts only #, ., ?, and /");
|
||||||
|
++dx;
|
||||||
|
}
|
||||||
|
if (offsets.empty() || offsets.size() > sr::kMaxPatternCells) {
|
||||||
|
throw std::runtime_error("pattern must contain 1..64 constrained cells");
|
||||||
|
}
|
||||||
|
return offsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sr::CellTerm> makeTerms(const Args& args) {
|
||||||
|
std::vector<sr::CellTerm> terms;
|
||||||
|
for (const CellOffset& cell : parsePattern(args)) {
|
||||||
|
terms.push_back({
|
||||||
|
sr::coordinateTerm(args.x + cell.dx, args.z + cell.dz),
|
||||||
|
cell.requirement,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return terms;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> roots18(const std::vector<sr::CellTerm>& terms) {
|
||||||
|
std::vector<uint32_t> roots;
|
||||||
|
for (uint32_t seed = 0; seed < (1U << 18); ++seed) {
|
||||||
|
if (sr::partialPatternPossible(seed, 18, terms.data(), int(terms.size()))) {
|
||||||
|
roots.push_back(seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VectorEmitter {
|
||||||
|
std::vector<uint64_t>& values;
|
||||||
|
void operator()(uint64_t seed) { values.push_back(seed); }
|
||||||
|
};
|
||||||
|
|
||||||
|
int commandVerify(const Args& args, const std::vector<sr::CellTerm>& terms) {
|
||||||
|
const bool match = sr::verifyPattern(args.seed & sr::kMask48, terms.data(), int(terms.size()));
|
||||||
|
std::cout << "seed48=" << (args.seed & sr::kMask48) << ' '
|
||||||
|
<< (match ? "match" : "no-match") << '\n';
|
||||||
|
return match ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int commandUpper(const Args& args, const std::vector<sr::CellTerm>& terms) {
|
||||||
|
std::vector<uint64_t> results;
|
||||||
|
VectorEmitter emit{results};
|
||||||
|
sr::solveUpper13(args.lower35, terms.data(), int(terms.size()), emit);
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
std::cout << "lower35=" << (args.lower35 & ((1ULL << 35) - 1ULL))
|
||||||
|
<< " exact_matches=" << results.size() << '\n';
|
||||||
|
for (uint64_t seed : results) std::cout << seed << '\n';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int commandRoots(const Args& args, const std::vector<sr::CellTerm>& terms) {
|
||||||
|
std::vector<uint32_t> roots = roots18(terms);
|
||||||
|
std::cout << "bits=18 candidates=" << roots.size() << '\n';
|
||||||
|
for (int bits = 19; bits <= args.bits; ++bits) {
|
||||||
|
const uint32_t high = 1U << (bits - 1);
|
||||||
|
const size_t oldSize = roots.size();
|
||||||
|
roots.reserve(oldSize * 2);
|
||||||
|
for (size_t i = 0; i < oldSize; ++i) roots.push_back(roots[i] | high);
|
||||||
|
roots.erase(std::remove_if(roots.begin(), roots.end(), [&](uint32_t seed) {
|
||||||
|
return !sr::partialPatternPossible(seed, bits, terms.data(), int(terms.size()));
|
||||||
|
}), roots.end());
|
||||||
|
std::cout << "bits=" << bits << " candidates=" << roots.size() << '\n';
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int commandSearch(const Args& args, const std::vector<sr::CellTerm>& terms) {
|
||||||
|
const std::vector<uint32_t> roots = roots18(terms);
|
||||||
|
constexpr uint64_t extensions = 1ULL << (35 - 18);
|
||||||
|
const uint64_t workItems = uint64_t(roots.size()) * extensions;
|
||||||
|
std::atomic<uint64_t> next{0};
|
||||||
|
std::atomic<uint64_t> totalResults{0};
|
||||||
|
std::mutex resultMutex;
|
||||||
|
std::vector<uint64_t> results;
|
||||||
|
const auto start = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
auto worker = [&] {
|
||||||
|
std::vector<uint64_t> local;
|
||||||
|
local.reserve(64);
|
||||||
|
VectorEmitter emit{local};
|
||||||
|
constexpr uint64_t batch = 256;
|
||||||
|
for (;;) {
|
||||||
|
const uint64_t begin = next.fetch_add(batch, std::memory_order_relaxed);
|
||||||
|
if (begin >= workItems) break;
|
||||||
|
const uint64_t end = std::min(begin + batch, workItems);
|
||||||
|
for (uint64_t index = begin; index < end; ++index) {
|
||||||
|
const uint64_t rootIndex = index >> (35 - 18);
|
||||||
|
const uint64_t extension = index & (extensions - 1);
|
||||||
|
const uint64_t lower35 = roots[rootIndex] | (extension << 18);
|
||||||
|
sr::solveUpper13(lower35, terms.data(), int(terms.size()), emit);
|
||||||
|
}
|
||||||
|
if (!local.empty()) {
|
||||||
|
totalResults.fetch_add(local.size(), std::memory_order_relaxed);
|
||||||
|
std::lock_guard<std::mutex> lock(resultMutex);
|
||||||
|
const size_t room = results.size() < args.maxResults
|
||||||
|
? size_t(args.maxResults - results.size()) : 0;
|
||||||
|
const size_t take = std::min(room, local.size());
|
||||||
|
results.insert(results.end(), local.begin(), local.begin() + take);
|
||||||
|
local.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
for (int i = 0; i < args.threads; ++i) threads.emplace_back(worker);
|
||||||
|
for (std::thread& thread : threads) thread.join();
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
const std::chrono::duration<double> elapsed = std::chrono::steady_clock::now() - start;
|
||||||
|
std::cout << "lower18_roots=" << roots.size() << " lower35_checked=" << workItems
|
||||||
|
<< " exact_matches=" << totalResults.load() << " seconds=" << elapsed.count() << '\n';
|
||||||
|
for (uint64_t seed : results) std::cout << seed << '\n';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
try {
|
||||||
|
const Args args = parseArgs(argc, argv);
|
||||||
|
const std::vector<sr::CellTerm> terms = makeTerms(args);
|
||||||
|
if (args.command == "verify") return commandVerify(args, terms);
|
||||||
|
if (args.command == "upper") return commandUpper(args, terms);
|
||||||
|
if (args.command == "roots") return commandRoots(args, terms);
|
||||||
|
if (args.command == "search") return commandSearch(args, terms);
|
||||||
|
usage();
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
std::cerr << "error: " << error.what() << '\n';
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
210
research/slime_pattern_cuda.cu
Normal file
210
research/slime_pattern_cuda.cu
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
#include "slime_solver_core.hpp"
|
||||||
|
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr = slime_research;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#define CUDA_CHECK(call) do { \
|
||||||
|
const cudaError_t error_ = (call); \
|
||||||
|
if (error_ != cudaSuccess) { \
|
||||||
|
std::cerr << #call << ": " << cudaGetErrorString(error_) << '\n'; \
|
||||||
|
std::exit(1); \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
struct Args {
|
||||||
|
int32_t x = 0;
|
||||||
|
int32_t z = 0;
|
||||||
|
int32_t size = 5;
|
||||||
|
std::string pattern;
|
||||||
|
int threads = 128;
|
||||||
|
uint64_t maxResults = 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CellOffset {
|
||||||
|
int32_t dx;
|
||||||
|
int32_t dz;
|
||||||
|
uint8_t requirement;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[noreturn]] void usage(int code = 1) {
|
||||||
|
std::cerr << "Usage: slime_pattern_cuda --x X --z Z "
|
||||||
|
"[-n N|--pattern ROW/ROW] [--threads N] [--max-results N]\n";
|
||||||
|
std::exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* value(int argc, char** argv, int& i) {
|
||||||
|
if (++i >= argc) usage();
|
||||||
|
return argv[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
Args parseArgs(int argc, char** argv) {
|
||||||
|
Args args;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
const std::string arg = argv[i];
|
||||||
|
if (arg == "--x") args.x = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--z") args.z = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "-n" || arg == "--size") args.size = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--pattern") args.pattern = value(argc, argv, i);
|
||||||
|
else if (arg == "--threads") args.threads = std::stoi(value(argc, argv, i));
|
||||||
|
else if (arg == "--max-results") args.maxResults = std::stoull(value(argc, argv, i));
|
||||||
|
else if (arg == "--help") usage(0);
|
||||||
|
else usage();
|
||||||
|
}
|
||||||
|
if (args.size <= 0 || int64_t(args.size) * args.size > sr::kMaxPatternCells ||
|
||||||
|
args.threads <= 0 || args.threads > 1024) usage();
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sr::CellTerm> makeTerms(const Args& args) {
|
||||||
|
std::vector<CellOffset> offsets;
|
||||||
|
if (args.pattern.empty()) {
|
||||||
|
for (int32_t dz = 0; dz < args.size; ++dz) {
|
||||||
|
for (int32_t dx = 0; dx < args.size; ++dx) {
|
||||||
|
offsets.push_back({dx, dz, sr::kSlime});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int32_t dx = 0;
|
||||||
|
int32_t dz = 0;
|
||||||
|
int32_t width = -1;
|
||||||
|
for (size_t i = 0; i <= args.pattern.size(); ++i) {
|
||||||
|
const char c = i == args.pattern.size() ? '/' : args.pattern[i];
|
||||||
|
if (c == '/') {
|
||||||
|
if (dx == 0 || (width >= 0 && dx != width)) {
|
||||||
|
throw std::runtime_error("pattern rows must have equal non-zero width");
|
||||||
|
}
|
||||||
|
width = dx;
|
||||||
|
dx = 0;
|
||||||
|
++dz;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '#') offsets.push_back({dx, dz, sr::kSlime});
|
||||||
|
else if (c == '.') offsets.push_back({dx, dz, sr::kNonSlime});
|
||||||
|
else if (c != '?') throw std::runtime_error("pattern accepts only #, ., ?, and /");
|
||||||
|
++dx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offsets.empty() || offsets.size() > sr::kMaxPatternCells) {
|
||||||
|
throw std::runtime_error("pattern must contain 1..64 constrained cells");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sr::CellTerm> terms;
|
||||||
|
for (const CellOffset& cell : offsets) {
|
||||||
|
terms.push_back({
|
||||||
|
sr::coordinateTerm(args.x + cell.dx, args.z + cell.dz),
|
||||||
|
cell.requirement,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return terms;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> roots18(const std::vector<sr::CellTerm>& terms) {
|
||||||
|
std::vector<uint32_t> roots;
|
||||||
|
for (uint32_t seed = 0; seed < (1U << 18); ++seed) {
|
||||||
|
if (sr::partialPatternPossible(seed, 18, terms.data(), int(terms.size()))) {
|
||||||
|
roots.push_back(seed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeviceEmitter {
|
||||||
|
unsigned long long* count;
|
||||||
|
uint64_t* results;
|
||||||
|
uint64_t capacity;
|
||||||
|
|
||||||
|
__device__ void operator()(uint64_t seed) {
|
||||||
|
const unsigned long long index = atomicAdd(count, 1ULL);
|
||||||
|
if (index < capacity) results[index] = seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
__global__ void solveKernel(
|
||||||
|
const uint32_t* roots,
|
||||||
|
uint64_t workItems,
|
||||||
|
const sr::CellTerm* cells,
|
||||||
|
int cellCount,
|
||||||
|
unsigned long long* resultCount,
|
||||||
|
uint64_t* results,
|
||||||
|
uint64_t resultCapacity
|
||||||
|
) {
|
||||||
|
constexpr uint64_t extensions = 1ULL << (35 - 18);
|
||||||
|
const uint64_t index = uint64_t(blockIdx.x) * blockDim.x + threadIdx.x;
|
||||||
|
if (index >= workItems) return;
|
||||||
|
const uint64_t rootIndex = index >> (35 - 18);
|
||||||
|
const uint64_t extension = index & (extensions - 1ULL);
|
||||||
|
const uint64_t lower35 = roots[rootIndex] | (extension << 18);
|
||||||
|
DeviceEmitter emit{resultCount, results, resultCapacity};
|
||||||
|
sr::solveUpper13(lower35, cells, cellCount, emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
const Args args = parseArgs(argc, argv);
|
||||||
|
const std::vector<sr::CellTerm> terms = makeTerms(args);
|
||||||
|
const std::vector<uint32_t> roots = roots18(terms);
|
||||||
|
const uint64_t workItems = uint64_t(roots.size()) << (35 - 18);
|
||||||
|
if (workItems == 0) {
|
||||||
|
std::cout << "lower18_roots=0 lower35_checked=0 exact_matches=0 milliseconds=0\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t* deviceRoots = nullptr;
|
||||||
|
sr::CellTerm* deviceTerms = nullptr;
|
||||||
|
uint64_t* deviceResults = nullptr;
|
||||||
|
unsigned long long* deviceCount = nullptr;
|
||||||
|
CUDA_CHECK(cudaMalloc(&deviceRoots, roots.size() * sizeof(uint32_t)));
|
||||||
|
CUDA_CHECK(cudaMalloc(&deviceTerms, terms.size() * sizeof(sr::CellTerm)));
|
||||||
|
CUDA_CHECK(cudaMalloc(&deviceResults, args.maxResults * sizeof(uint64_t)));
|
||||||
|
CUDA_CHECK(cudaMalloc(&deviceCount, sizeof(unsigned long long)));
|
||||||
|
CUDA_CHECK(cudaMemcpy(deviceRoots, roots.data(), roots.size() * sizeof(uint32_t), cudaMemcpyHostToDevice));
|
||||||
|
CUDA_CHECK(cudaMemcpy(deviceTerms, terms.data(), terms.size() * sizeof(sr::CellTerm), cudaMemcpyHostToDevice));
|
||||||
|
CUDA_CHECK(cudaMemset(deviceCount, 0, sizeof(unsigned long long)));
|
||||||
|
|
||||||
|
cudaEvent_t start;
|
||||||
|
cudaEvent_t stop;
|
||||||
|
CUDA_CHECK(cudaEventCreate(&start));
|
||||||
|
CUDA_CHECK(cudaEventCreate(&stop));
|
||||||
|
CUDA_CHECK(cudaEventRecord(start));
|
||||||
|
const uint64_t blocks64 = (workItems + uint64_t(args.threads) - 1) / uint64_t(args.threads);
|
||||||
|
if (blocks64 > 0x7FFFFFFFULL) throw std::runtime_error("grid exceeds prototype limit");
|
||||||
|
solveKernel<<<static_cast<unsigned>(blocks64), args.threads>>>(
|
||||||
|
deviceRoots, workItems, deviceTerms, int(terms.size()), deviceCount,
|
||||||
|
deviceResults, args.maxResults
|
||||||
|
);
|
||||||
|
CUDA_CHECK(cudaGetLastError());
|
||||||
|
CUDA_CHECK(cudaEventRecord(stop));
|
||||||
|
CUDA_CHECK(cudaEventSynchronize(stop));
|
||||||
|
|
||||||
|
unsigned long long count = 0;
|
||||||
|
CUDA_CHECK(cudaMemcpy(&count, deviceCount, sizeof(count), cudaMemcpyDeviceToHost));
|
||||||
|
const size_t stored = size_t(std::min<unsigned long long>(count, args.maxResults));
|
||||||
|
std::vector<uint64_t> results(stored);
|
||||||
|
CUDA_CHECK(cudaMemcpy(results.data(), deviceResults, stored * sizeof(uint64_t), cudaMemcpyDeviceToHost));
|
||||||
|
std::sort(results.begin(), results.end());
|
||||||
|
float milliseconds = 0.0F;
|
||||||
|
CUDA_CHECK(cudaEventElapsedTime(&milliseconds, start, stop));
|
||||||
|
|
||||||
|
std::cout << "lower18_roots=" << roots.size() << " lower35_checked=" << workItems
|
||||||
|
<< " exact_matches=" << count << " milliseconds=" << milliseconds << '\n';
|
||||||
|
for (uint64_t seed : results) std::cout << seed << '\n';
|
||||||
|
|
||||||
|
cudaEventDestroy(start);
|
||||||
|
cudaEventDestroy(stop);
|
||||||
|
cudaFree(deviceRoots);
|
||||||
|
cudaFree(deviceTerms);
|
||||||
|
cudaFree(deviceResults);
|
||||||
|
cudaFree(deviceCount);
|
||||||
|
}
|
||||||
484
research/slime_pattern_prototype.py
Normal file
484
research/slime_pattern_prototype.py
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Research prototype for reverse-searching Minecraft slime patterns.
|
||||||
|
|
||||||
|
The prototype deliberately focuses on the seed side of the problem at one
|
||||||
|
fixed top-left chunk coordinate:
|
||||||
|
|
||||||
|
* exact Java integer and ``Random.nextInt(10)`` verification;
|
||||||
|
* arbitrary patterns (slime, non-slime, or ignored cells);
|
||||||
|
* 2-adic/bit lifting of lower seed prefixes;
|
||||||
|
* an analytic solver for the 13 seed bits above bit 35.
|
||||||
|
|
||||||
|
The analytic solver models the first ``next(31)`` draw. Every result is then
|
||||||
|
checked with the exact rejection-loop implementation, so it has no false
|
||||||
|
positives. A first draw in Java's eight-value rejection tail can theoretically
|
||||||
|
create a false negative; see README.md for the exact scope of this prototype.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Iterable, Iterator, Sequence
|
||||||
|
|
||||||
|
JAVA_MULT = 0x5DEECE66D
|
||||||
|
JAVA_ADD = 0xB
|
||||||
|
MASK48 = (1 << 48) - 1
|
||||||
|
SLIME_XOR = 987234911
|
||||||
|
COMBINED_XOR = SLIME_XOR ^ JAVA_MULT
|
||||||
|
|
||||||
|
X2_COEFF = 4_987_142
|
||||||
|
X_COEFF = 5_947_611
|
||||||
|
Z2_COEFF = 4_392_871
|
||||||
|
Z_COEFF = 389_711
|
||||||
|
|
||||||
|
LOWER_BITS = 35
|
||||||
|
UPPER_BITS = 48 - LOWER_BITS
|
||||||
|
UPPER_SIZE = 1 << UPPER_BITS
|
||||||
|
UPPER_MASK = UPPER_SIZE - 1
|
||||||
|
UPPER_STEP = 1 << LOWER_BITS
|
||||||
|
COARSE_DELTA = 3_290
|
||||||
|
HALF_DELTA = COARSE_DELTA // 2 # 1645
|
||||||
|
HALF_DELTA_INVERSE = pow(HALF_DELTA, -1, UPPER_SIZE) # 4965
|
||||||
|
X_SIGNATURE_PERIOD = 1 << 30
|
||||||
|
Z_SIGNATURE_PERIOD = 1 << 31
|
||||||
|
X_SIGNATURE_INVERSE = pow((2 * X2_COEFF) // 4, -1, X_SIGNATURE_PERIOD)
|
||||||
|
Z_SIGNATURE_INVERSE = pow(Z2_COEFF, -1, Z_SIGNATURE_PERIOD)
|
||||||
|
|
||||||
|
|
||||||
|
class Requirement(Enum):
|
||||||
|
SLIME = "#"
|
||||||
|
NON_SLIME = "."
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Cell:
|
||||||
|
dx: int
|
||||||
|
dz: int
|
||||||
|
requirement: Requirement
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Pattern:
|
||||||
|
cells: tuple[Cell, ...]
|
||||||
|
width: int
|
||||||
|
height: int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, text: str) -> "Pattern":
|
||||||
|
text = text.replace("/", "\n")
|
||||||
|
rows = ["".join(row.split()) for row in text.strip().splitlines() if row.strip()]
|
||||||
|
if not rows:
|
||||||
|
raise ValueError("pattern is empty")
|
||||||
|
width = len(rows[0])
|
||||||
|
if width == 0 or any(len(row) != width for row in rows):
|
||||||
|
raise ValueError("pattern rows must have equal non-zero width")
|
||||||
|
|
||||||
|
cells: list[Cell] = []
|
||||||
|
for dz, row in enumerate(rows):
|
||||||
|
for dx, char in enumerate(row):
|
||||||
|
if char == "#":
|
||||||
|
cells.append(Cell(dx, dz, Requirement.SLIME))
|
||||||
|
elif char == ".":
|
||||||
|
cells.append(Cell(dx, dz, Requirement.NON_SLIME))
|
||||||
|
elif char != "?":
|
||||||
|
raise ValueError(f"unknown pattern character {char!r}; use #, ., or ?")
|
||||||
|
if not cells:
|
||||||
|
raise ValueError("pattern has no constraints")
|
||||||
|
return cls(tuple(cells), width, len(rows))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def solid(cls, size: int) -> "Pattern":
|
||||||
|
if size <= 0:
|
||||||
|
raise ValueError("size must be positive")
|
||||||
|
return cls.parse("\n".join("#" * size for _ in range(size)))
|
||||||
|
|
||||||
|
|
||||||
|
def int32(value: int) -> int:
|
||||||
|
value &= 0xFFFFFFFF
|
||||||
|
return value - (1 << 32) if value & 0x80000000 else value
|
||||||
|
|
||||||
|
|
||||||
|
def coordinate_term(chunk_x: int, chunk_z: int) -> int:
|
||||||
|
"""Reproduce the mixed int/long arithmetic in Minecraft's slime formula."""
|
||||||
|
return (
|
||||||
|
int32(chunk_x * chunk_x * X2_COEFF)
|
||||||
|
+ int32(chunk_x * X_COEFF)
|
||||||
|
+ int32(chunk_z * chunk_z) * Z2_COEFF
|
||||||
|
+ int32(chunk_z * Z_COEFF)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def position_signature32(chunk_x: int, chunk_z: int) -> tuple[int, int]:
|
||||||
|
"""Return the two slopes that determine all relative terms modulo 2^32."""
|
||||||
|
mask32 = (1 << 32) - 1
|
||||||
|
hx = (2 * X2_COEFF * chunk_x + X2_COEFF + X_COEFF) & mask32
|
||||||
|
hz = (2 * Z2_COEFF * chunk_z + Z2_COEFF + Z_COEFF) & mask32
|
||||||
|
return hx, hz
|
||||||
|
|
||||||
|
|
||||||
|
def coordinate_residues_from_signature32(hx: int, hz: int) -> tuple[int, int]:
|
||||||
|
"""Invert an Hx/Hz signature to x mod 2^30 and z mod 2^31.
|
||||||
|
|
||||||
|
Hx has two fixed low bits because v2(2*X2_COEFF)=2; Hz has one
|
||||||
|
fixed low bit because v2(2*Z2_COEFF)=1.
|
||||||
|
"""
|
||||||
|
mask32 = (1 << 32) - 1
|
||||||
|
x_difference = (hx - X2_COEFF - X_COEFF) & mask32
|
||||||
|
z_difference = (hz - Z2_COEFF - Z_COEFF) & mask32
|
||||||
|
if x_difference & 3:
|
||||||
|
raise ValueError("invalid Hx signature")
|
||||||
|
if z_difference & 1:
|
||||||
|
raise ValueError("invalid Hz signature")
|
||||||
|
x = ((x_difference >> 2) * X_SIGNATURE_INVERSE) % X_SIGNATURE_PERIOD
|
||||||
|
z = ((z_difference >> 1) * Z_SIGNATURE_INVERSE) % Z_SIGNATURE_PERIOD
|
||||||
|
return x, z
|
||||||
|
|
||||||
|
|
||||||
|
def congruent_in_interval(residue: int, period: int, lower: int, upper: int) -> list[int]:
|
||||||
|
"""List integers in an inclusive interval belonging to one residue class."""
|
||||||
|
first = lower + ((residue - lower) % period)
|
||||||
|
return list(range(first, upper + 1, period))
|
||||||
|
|
||||||
|
|
||||||
|
def positions_from_signature32(
|
||||||
|
hx: int,
|
||||||
|
hz: int,
|
||||||
|
lower: int = -1_875_000,
|
||||||
|
upper: int = 1_874_999,
|
||||||
|
) -> list[tuple[int, int]]:
|
||||||
|
"""Map a valid signature back to positions inside a square world bound."""
|
||||||
|
xr, zr = coordinate_residues_from_signature32(hx, hz)
|
||||||
|
xs = congruent_in_interval(xr, X_SIGNATURE_PERIOD, lower, upper)
|
||||||
|
zs = congruent_in_interval(zr, Z_SIGNATURE_PERIOD, lower, upper)
|
||||||
|
return [(x, z) for x in xs for z in zs]
|
||||||
|
|
||||||
|
|
||||||
|
def outer_sum_delta32(chunk_x: int, chunk_z: int, dx: int, dz: int) -> int:
|
||||||
|
"""Compute T(x+dx,z+dz)-T(x,z) through its X/Z outer-sum form."""
|
||||||
|
hx, hz = position_signature32(chunk_x, chunk_z)
|
||||||
|
return (
|
||||||
|
dx * hx
|
||||||
|
+ X2_COEFF * dx * (dx - 1)
|
||||||
|
+ dz * hz
|
||||||
|
+ Z2_COEFF * dz * (dz - 1)
|
||||||
|
) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def next_state(seed48: int, term: int) -> int:
|
||||||
|
state = ((seed48 + term) ^ COMBINED_XOR) & MASK48
|
||||||
|
return (state * JAVA_MULT + JAVA_ADD) & MASK48
|
||||||
|
|
||||||
|
|
||||||
|
def effective_base(seed48: int, chunk_x: int, chunk_z: int) -> int:
|
||||||
|
"""Absorb the absolute coordinate term into the 48-bit world seed."""
|
||||||
|
return (seed48 + coordinate_term(chunk_x, chunk_z)) & MASK48
|
||||||
|
|
||||||
|
|
||||||
|
def relative_term48(chunk_x: int, chunk_z: int, dx: int, dz: int) -> int:
|
||||||
|
"""Exact 48-bit relative coordinate term, including Java sign extension."""
|
||||||
|
return (
|
||||||
|
coordinate_term(chunk_x + dx, chunk_z + dz)
|
||||||
|
- coordinate_term(chunk_x, chunk_z)
|
||||||
|
) & MASK48
|
||||||
|
|
||||||
|
|
||||||
|
def exact_is_slime_effective(base48: int, relative_term: int) -> bool:
|
||||||
|
"""Evaluate a cell after reparameterizing seed+T(x,z) as base48."""
|
||||||
|
state = ((base48 + relative_term) ^ COMBINED_XOR) & MASK48
|
||||||
|
while True:
|
||||||
|
state = (state * JAVA_MULT + JAVA_ADD) & MASK48
|
||||||
|
bits = state >> 17
|
||||||
|
value = bits % 10
|
||||||
|
if bits - value + 9 <= 0x7FFFFFFF:
|
||||||
|
return value == 0
|
||||||
|
|
||||||
|
|
||||||
|
def first_draw(seed48: int, chunk_x: int, chunk_z: int) -> int:
|
||||||
|
return next_state(seed48, coordinate_term(chunk_x, chunk_z)) >> 17
|
||||||
|
|
||||||
|
|
||||||
|
def exact_is_slime(seed48: int, chunk_x: int, chunk_z: int) -> bool:
|
||||||
|
"""Evaluate Java Random.nextInt(10) including its rejection loop."""
|
||||||
|
state = ((seed48 + coordinate_term(chunk_x, chunk_z)) ^ COMBINED_XOR) & MASK48
|
||||||
|
while True:
|
||||||
|
state = (state * JAVA_MULT + JAVA_ADD) & MASK48
|
||||||
|
bits = state >> 17
|
||||||
|
value = bits % 10
|
||||||
|
if bits - value + 9 <= 0x7FFFFFFF:
|
||||||
|
return value == 0
|
||||||
|
|
||||||
|
|
||||||
|
def verify_pattern(seed48: int, chunk_x: int, chunk_z: int, pattern: Pattern) -> bool:
|
||||||
|
for cell in pattern.cells:
|
||||||
|
actual = exact_is_slime(seed48, chunk_x + cell.dx, chunk_z + cell.dz)
|
||||||
|
if actual != (cell.requirement is Requirement.SLIME):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def cell_terms(chunk_x: int, chunk_z: int, pattern: Pattern) -> tuple[tuple[Cell, int], ...]:
|
||||||
|
return tuple(
|
||||||
|
(cell, coordinate_term(chunk_x + cell.dx, chunk_z + cell.dz))
|
||||||
|
for cell in pattern.cells
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def partial_pattern_possible(
|
||||||
|
seed_prefix: int,
|
||||||
|
known_seed_bits: int,
|
||||||
|
terms: Sequence[tuple[Cell, int]],
|
||||||
|
) -> bool:
|
||||||
|
"""Return whether a seed prefix can satisfy the first-draw constraints.
|
||||||
|
|
||||||
|
At 18..47 known seed bits, a required slime result can still become a
|
||||||
|
multiple of ten iff the known low output is even. A non-slime result
|
||||||
|
always has some completion while output bits remain unknown.
|
||||||
|
"""
|
||||||
|
if not 18 <= known_seed_bits <= 48:
|
||||||
|
raise ValueError("known_seed_bits must be in [18, 48]")
|
||||||
|
mask = (1 << known_seed_bits) - 1
|
||||||
|
seed_prefix &= mask
|
||||||
|
for cell, term in terms:
|
||||||
|
state = (((seed_prefix + term) ^ COMBINED_XOR) * JAVA_MULT + JAVA_ADD) & mask
|
||||||
|
known_output = state >> 17
|
||||||
|
if known_seed_bits == 48:
|
||||||
|
is_slime = known_output % 10 == 0
|
||||||
|
if is_slime != (cell.requirement is Requirement.SLIME):
|
||||||
|
return False
|
||||||
|
elif cell.requirement is Requirement.SLIME and known_output & 1:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def initial_roots(
|
||||||
|
chunk_x: int,
|
||||||
|
chunk_z: int,
|
||||||
|
pattern: Pattern,
|
||||||
|
start_bits: int = 18,
|
||||||
|
) -> list[int]:
|
||||||
|
terms = cell_terms(chunk_x, chunk_z, pattern)
|
||||||
|
return [
|
||||||
|
seed
|
||||||
|
for seed in range(1 << start_bits)
|
||||||
|
if partial_pattern_possible(seed, start_bits, terms)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def lift_prefixes(
|
||||||
|
prefixes: Iterable[int],
|
||||||
|
from_bits: int,
|
||||||
|
to_bits: int,
|
||||||
|
terms: Sequence[tuple[Cell, int]],
|
||||||
|
max_candidates: int | None = None,
|
||||||
|
) -> list[int]:
|
||||||
|
"""Lift lower-bit seed classes one bit at a time."""
|
||||||
|
if not 18 <= from_bits <= to_bits <= LOWER_BITS:
|
||||||
|
raise ValueError("expected 18 <= from_bits <= to_bits <= 35")
|
||||||
|
current = list(prefixes)
|
||||||
|
for bits in range(from_bits + 1, to_bits + 1):
|
||||||
|
high_bit = 1 << (bits - 1)
|
||||||
|
children = (
|
||||||
|
child
|
||||||
|
for prefix in current
|
||||||
|
for child in (prefix, prefix | high_bit)
|
||||||
|
)
|
||||||
|
current = [child for child in children if partial_pattern_possible(child, bits, terms)]
|
||||||
|
if max_candidates is not None and len(current) > max_candidates:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"lifting reached {len(current):,} candidates at {bits} bits; "
|
||||||
|
f"raise --max-candidates to continue"
|
||||||
|
)
|
||||||
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class UpperConstraint:
|
||||||
|
threshold: int
|
||||||
|
residue_before: int
|
||||||
|
residue_after: int
|
||||||
|
|
||||||
|
def residue_at(self, n: int) -> int:
|
||||||
|
return self.residue_before if n < self.threshold else self.residue_after
|
||||||
|
|
||||||
|
|
||||||
|
def upper_constraint(lower35: int, term: int) -> UpperConstraint | None:
|
||||||
|
"""Convert one required-slime cell into a piecewise n mod 5 constraint."""
|
||||||
|
output = next_state(lower35, term) >> 17
|
||||||
|
low17 = output & ((1 << 17) - 1)
|
||||||
|
if low17 & 1:
|
||||||
|
return None
|
||||||
|
|
||||||
|
upper14 = output >> 17
|
||||||
|
parity = upper14 & 1
|
||||||
|
v0 = (upper14 - parity) // 2
|
||||||
|
wanted_v_mod5 = (3 * (-(low17 // 2) - parity)) % 5
|
||||||
|
before = (wanted_v_mod5 - v0) % 5
|
||||||
|
return UpperConstraint(
|
||||||
|
threshold=UPPER_SIZE - v0,
|
||||||
|
residue_before=before,
|
||||||
|
residue_after=(before + (UPPER_SIZE % 5)) % 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def values_with_residue(start: int, stop: int, residue: int) -> Iterator[int]:
|
||||||
|
first = start + ((residue - start) % 5)
|
||||||
|
yield from range(first, stop, 5)
|
||||||
|
|
||||||
|
|
||||||
|
def solve_upper13_candidates(
|
||||||
|
lower35: int,
|
||||||
|
terms: Sequence[tuple[Cell, int]],
|
||||||
|
) -> list[int]:
|
||||||
|
"""Solve all upper 13 bits satisfying the first-draw slime constraints."""
|
||||||
|
lower35 &= (1 << LOWER_BITS) - 1
|
||||||
|
constraints: list[UpperConstraint] = []
|
||||||
|
for cell, term in terms:
|
||||||
|
if cell.requirement is not Requirement.SLIME:
|
||||||
|
continue
|
||||||
|
constraint = upper_constraint(lower35, term)
|
||||||
|
if constraint is None:
|
||||||
|
return []
|
||||||
|
constraints.append(constraint)
|
||||||
|
|
||||||
|
if not constraints:
|
||||||
|
candidate_n = range(UPPER_SIZE)
|
||||||
|
else:
|
||||||
|
boundaries = sorted({0, UPPER_SIZE, *(item.threshold for item in constraints)})
|
||||||
|
n_values: list[int] = []
|
||||||
|
for start, stop in zip(boundaries, boundaries[1:]):
|
||||||
|
if start == stop:
|
||||||
|
continue
|
||||||
|
residues = {item.residue_at(start) for item in constraints}
|
||||||
|
if len(residues) == 1:
|
||||||
|
n_values.extend(values_with_residue(start, stop, residues.pop()))
|
||||||
|
candidate_n = n_values
|
||||||
|
|
||||||
|
seeds = {
|
||||||
|
lower35 + (((HALF_DELTA_INVERSE * n) & UPPER_MASK) << LOWER_BITS)
|
||||||
|
for n in candidate_n
|
||||||
|
}
|
||||||
|
return sorted(seeds)
|
||||||
|
|
||||||
|
|
||||||
|
def solve_upper13_exact(
|
||||||
|
lower35: int,
|
||||||
|
chunk_x: int,
|
||||||
|
chunk_z: int,
|
||||||
|
pattern: Pattern,
|
||||||
|
) -> list[int]:
|
||||||
|
terms = cell_terms(chunk_x, chunk_z, pattern)
|
||||||
|
return [
|
||||||
|
seed
|
||||||
|
for seed in solve_upper13_candidates(lower35, terms)
|
||||||
|
if verify_pattern(seed, chunk_x, chunk_z, pattern)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pattern(args: argparse.Namespace) -> Pattern:
|
||||||
|
if args.pattern is not None:
|
||||||
|
return Pattern.parse(args.pattern.replace("/", "\n"))
|
||||||
|
return Pattern.solid(args.size)
|
||||||
|
|
||||||
|
|
||||||
|
def add_pattern_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
|
parser.add_argument("-n", "--size", type=int, default=5, help="solid square size")
|
||||||
|
parser.add_argument(
|
||||||
|
"--pattern",
|
||||||
|
help="rows separated by '/', using #=slime, .=non-slime, ?=ignored",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def command_verify(args: argparse.Namespace) -> int:
|
||||||
|
pattern = parse_pattern(args)
|
||||||
|
ok = verify_pattern(args.seed & MASK48, args.x, args.z, pattern)
|
||||||
|
print(f"seed48={args.seed & MASK48} x={args.x} z={args.z}: {'match' if ok else 'no match'}")
|
||||||
|
return 0 if ok else 1
|
||||||
|
|
||||||
|
|
||||||
|
def command_upper(args: argparse.Namespace) -> int:
|
||||||
|
pattern = parse_pattern(args)
|
||||||
|
lower35 = args.lower35 & ((1 << LOWER_BITS) - 1)
|
||||||
|
seeds = solve_upper13_exact(lower35, args.x, args.z, pattern)
|
||||||
|
print(f"lower35={lower35} exact_matches={len(seeds)}")
|
||||||
|
for seed in seeds:
|
||||||
|
print(seed)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def command_roots(args: argparse.Namespace) -> int:
|
||||||
|
pattern = parse_pattern(args)
|
||||||
|
terms = cell_terms(args.x, args.z, pattern)
|
||||||
|
roots = initial_roots(args.x, args.z, pattern)
|
||||||
|
print(f"bits=18 candidates={len(roots)}")
|
||||||
|
for bits in range(19, args.bits + 1):
|
||||||
|
roots = lift_prefixes(roots, bits - 1, bits, terms, args.max_candidates)
|
||||||
|
print(f"bits={bits} candidates={len(roots)}")
|
||||||
|
if args.print_values:
|
||||||
|
for root in roots:
|
||||||
|
print(root)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def command_signature(args: argparse.Namespace) -> int:
|
||||||
|
pattern = parse_pattern(args)
|
||||||
|
hx, hz = position_signature32(args.x, args.z)
|
||||||
|
print(f"Hx=0x{hx:08x} Hz=0x{hz:08x}")
|
||||||
|
print(f"world_positions={positions_from_signature32(hx, hz)}")
|
||||||
|
base = coordinate_term(args.x, args.z)
|
||||||
|
for cell in pattern.cells:
|
||||||
|
direct = (coordinate_term(args.x + cell.dx, args.z + cell.dz) - base) & 0xFFFFFFFF
|
||||||
|
outer = outer_sum_delta32(args.x, args.z, cell.dx, cell.dz)
|
||||||
|
exact48 = relative_term48(args.x, args.z, cell.dx, cell.dz)
|
||||||
|
print(
|
||||||
|
f"({cell.dx},{cell.dz}) {cell.requirement.value} "
|
||||||
|
f"delta32=0x{outer:08x} delta48=0x{exact48:012x}"
|
||||||
|
)
|
||||||
|
if direct != outer:
|
||||||
|
raise AssertionError("outer-sum identity failed")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
verify = subparsers.add_parser("verify", help="exactly verify one seed/pattern")
|
||||||
|
verify.add_argument("--seed", type=int, required=True)
|
||||||
|
verify.add_argument("--x", type=int, required=True)
|
||||||
|
verify.add_argument("--z", type=int, required=True)
|
||||||
|
add_pattern_arguments(verify)
|
||||||
|
verify.set_defaults(func=command_verify)
|
||||||
|
|
||||||
|
upper = subparsers.add_parser("upper", help="solve the 13 bits above a lower-35 prefix")
|
||||||
|
upper.add_argument("--lower35", type=int, required=True)
|
||||||
|
upper.add_argument("--x", type=int, required=True)
|
||||||
|
upper.add_argument("--z", type=int, required=True)
|
||||||
|
add_pattern_arguments(upper)
|
||||||
|
upper.set_defaults(func=command_upper)
|
||||||
|
|
||||||
|
roots = subparsers.add_parser("roots", help="enumerate/lift low seed prefixes")
|
||||||
|
roots.add_argument("--x", type=int, required=True)
|
||||||
|
roots.add_argument("--z", type=int, required=True)
|
||||||
|
roots.add_argument("--bits", type=int, default=24, choices=range(18, LOWER_BITS + 1))
|
||||||
|
roots.add_argument("--max-candidates", type=int, default=1_000_000)
|
||||||
|
roots.add_argument("--print-values", action="store_true")
|
||||||
|
add_pattern_arguments(roots)
|
||||||
|
roots.set_defaults(func=command_roots)
|
||||||
|
|
||||||
|
signature = subparsers.add_parser("signature", help="show/check the position outer sum")
|
||||||
|
signature.add_argument("--x", type=int, required=True)
|
||||||
|
signature.add_argument("--z", type=int, required=True)
|
||||||
|
add_pattern_arguments(signature)
|
||||||
|
signature.set_defaults(func=command_signature)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = build_parser().parse_args()
|
||||||
|
return args.func(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
263
research/slime_position_cuda.cu
Normal file
263
research/slime_position_cuda.cu
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
#include "slime_solver_core.hpp"
|
||||||
|
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sr = slime_research;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int kMaxSize = 8;
|
||||||
|
|
||||||
|
struct Hit {
|
||||||
|
int32_t x;
|
||||||
|
int32_t z;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool operator==(const Hit& a, const Hit& b) {
|
||||||
|
return a.x == b.x && a.z == b.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Args {
|
||||||
|
uint64_t seed = 61692151016558ULL;
|
||||||
|
int32_t x0 = 46973;
|
||||||
|
int32_t x1 = 55165;
|
||||||
|
int32_t z0 = 415028;
|
||||||
|
int32_t z1 = 423220;
|
||||||
|
int size = 5;
|
||||||
|
int threads = 256;
|
||||||
|
int repeats = 3;
|
||||||
|
uint32_t maxHits = 1024;
|
||||||
|
};
|
||||||
|
|
||||||
|
__device__ bool solidDirect(uint64_t seed, int32_t x, int32_t z, int n) {
|
||||||
|
for (int dx = 0; dx < n; ++dx) {
|
||||||
|
for (int dz = 0; dz < n; ++dz) {
|
||||||
|
if (!sr::exactIsSlimeFromTerm(seed, sr::coordinateTerm(x + dx, z + dz))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ void emitHit(Hit* hits, uint32_t* count, uint32_t capacity, int32_t x, int32_t z) {
|
||||||
|
const uint32_t index = atomicAdd(count, 1U);
|
||||||
|
if (index < capacity) {
|
||||||
|
hits[index] = Hit{x, z};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__global__ void directKernel(
|
||||||
|
uint64_t seed,
|
||||||
|
int32_t x0,
|
||||||
|
int32_t z0,
|
||||||
|
uint64_t width,
|
||||||
|
uint64_t height,
|
||||||
|
int n,
|
||||||
|
Hit* hits,
|
||||||
|
uint32_t* count,
|
||||||
|
uint32_t capacity
|
||||||
|
) {
|
||||||
|
const uint64_t stride = uint64_t(gridDim.x) * blockDim.x;
|
||||||
|
for (uint64_t index = uint64_t(blockIdx.x) * blockDim.x + threadIdx.x;
|
||||||
|
index < width * height;
|
||||||
|
index += stride) {
|
||||||
|
const int32_t x = x0 + static_cast<int32_t>(index / height);
|
||||||
|
const int32_t z = z0 + static_cast<int32_t>(index % height);
|
||||||
|
if (solidDirect(seed, x, z, n)) {
|
||||||
|
emitHit(hits, count, capacity, x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__global__ void outerKernel(
|
||||||
|
uint64_t seed,
|
||||||
|
int32_t x0,
|
||||||
|
int32_t z0,
|
||||||
|
uint64_t width,
|
||||||
|
uint64_t height,
|
||||||
|
int n,
|
||||||
|
Hit* hits,
|
||||||
|
uint32_t* count,
|
||||||
|
uint32_t capacity
|
||||||
|
) {
|
||||||
|
// One block covers a 16x16 position tile. The expanded one-dimensional
|
||||||
|
// axis terms are shared by all 256 candidate positions in the tile.
|
||||||
|
__shared__ uint64_t xTerms[16 + kMaxSize - 1];
|
||||||
|
__shared__ uint64_t zTerms[16 + kMaxSize - 1];
|
||||||
|
const int expanded = 16 + n - 1;
|
||||||
|
const int linearThread = threadIdx.y * blockDim.x + threadIdx.x;
|
||||||
|
if (linearThread < expanded) {
|
||||||
|
xTerms[linearThread] = sr::coordinateXTerm(
|
||||||
|
x0 + static_cast<int32_t>(uint64_t(blockIdx.x) * 16 + linearThread));
|
||||||
|
zTerms[linearThread] = sr::coordinateZTerm(
|
||||||
|
z0 + static_cast<int32_t>(uint64_t(blockIdx.y) * 16 + linearThread));
|
||||||
|
}
|
||||||
|
__syncthreads();
|
||||||
|
|
||||||
|
const uint64_t xi = uint64_t(blockIdx.x) * 16 + threadIdx.x;
|
||||||
|
const uint64_t zi = uint64_t(blockIdx.y) * 16 + threadIdx.y;
|
||||||
|
if (xi >= width || zi >= height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int dx = 0; dx < n; ++dx) {
|
||||||
|
for (int dz = 0; dz < n; ++dz) {
|
||||||
|
if (!sr::exactIsSlimeFromTerm(seed,
|
||||||
|
xTerms[threadIdx.x + dx] +
|
||||||
|
zTerms[threadIdx.y + dz])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emitHit(hits, count, capacity, x0 + static_cast<int32_t>(xi),
|
||||||
|
z0 + static_cast<int32_t>(zi));
|
||||||
|
}
|
||||||
|
|
||||||
|
void cudaCheck(cudaError_t status, const char* what) {
|
||||||
|
if (status != cudaSuccess) {
|
||||||
|
throw std::runtime_error(std::string(what) + ": " + cudaGetErrorString(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Args parseArgs(int argc, char** argv) {
|
||||||
|
Args args;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
const std::string key = argv[i];
|
||||||
|
if (key == "--help") {
|
||||||
|
std::cout
|
||||||
|
<< "Usage: slime_position_cuda [options]\n"
|
||||||
|
<< " --seed N --x0 N --x1 N --z0 N --z1 N\n"
|
||||||
|
<< " -n N --threads N --repeats N --max-hits N\n"
|
||||||
|
<< "Bounds are inclusive. Both kernels run and their hits must agree.\n";
|
||||||
|
std::exit(0);
|
||||||
|
}
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
throw std::runtime_error("missing value after " + key);
|
||||||
|
}
|
||||||
|
const std::string value = argv[++i];
|
||||||
|
if (key == "--seed") args.seed = std::stoull(value) & sr::kMask48;
|
||||||
|
else if (key == "--x0") args.x0 = std::stoi(value);
|
||||||
|
else if (key == "--x1") args.x1 = std::stoi(value);
|
||||||
|
else if (key == "--z0") args.z0 = std::stoi(value);
|
||||||
|
else if (key == "--z1") args.z1 = std::stoi(value);
|
||||||
|
else if (key == "-n" || key == "--size") args.size = std::stoi(value);
|
||||||
|
else if (key == "--threads") args.threads = std::stoi(value);
|
||||||
|
else if (key == "--repeats") args.repeats = std::stoi(value);
|
||||||
|
else if (key == "--max-hits") args.maxHits = std::stoul(value);
|
||||||
|
else throw std::runtime_error("unknown option " + key);
|
||||||
|
}
|
||||||
|
if (args.x1 < args.x0 || args.z1 < args.z0) throw std::runtime_error("invalid bounds");
|
||||||
|
if (args.size <= 0 || args.size > kMaxSize) throw std::runtime_error("size must be 1..8");
|
||||||
|
if (args.threads <= 0 || args.threads > 1024) throw std::runtime_error("invalid threads");
|
||||||
|
if (args.repeats <= 0 || args.maxHits == 0) throw std::runtime_error("invalid repeat/hit count");
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Mode { Direct, Outer };
|
||||||
|
|
||||||
|
struct RunResult {
|
||||||
|
float milliseconds;
|
||||||
|
uint32_t totalHits;
|
||||||
|
std::vector<Hit> storedHits;
|
||||||
|
};
|
||||||
|
|
||||||
|
RunResult run(const Args& args, Mode mode, Hit* deviceHits, uint32_t* deviceCount) {
|
||||||
|
const uint64_t width = uint64_t(int64_t(args.x1) - args.x0 + 1);
|
||||||
|
const uint64_t height = uint64_t(int64_t(args.z1) - args.z0 + 1);
|
||||||
|
cudaCheck(cudaMemset(deviceCount, 0, sizeof(uint32_t)), "reset count");
|
||||||
|
cudaEvent_t begin, end;
|
||||||
|
cudaCheck(cudaEventCreate(&begin), "create begin event");
|
||||||
|
cudaCheck(cudaEventCreate(&end), "create end event");
|
||||||
|
cudaCheck(cudaEventRecord(begin), "record begin");
|
||||||
|
if (mode == Mode::Direct) {
|
||||||
|
const uint64_t work = width * height;
|
||||||
|
const int blocks = static_cast<int>(std::min<uint64_t>((work + args.threads - 1) / args.threads, 65535));
|
||||||
|
directKernel<<<blocks, args.threads>>>(args.seed, args.x0, args.z0, width, height,
|
||||||
|
args.size, deviceHits, deviceCount, args.maxHits);
|
||||||
|
} else {
|
||||||
|
const dim3 threads(16, 16);
|
||||||
|
const dim3 blocks(static_cast<unsigned>((width + 15) / 16),
|
||||||
|
static_cast<unsigned>((height + 15) / 16));
|
||||||
|
outerKernel<<<blocks, threads>>>(args.seed, args.x0, args.z0, width, height,
|
||||||
|
args.size, deviceHits, deviceCount, args.maxHits);
|
||||||
|
}
|
||||||
|
cudaCheck(cudaGetLastError(), "launch kernel");
|
||||||
|
cudaCheck(cudaEventRecord(end), "record end");
|
||||||
|
cudaCheck(cudaEventSynchronize(end), "wait for kernel");
|
||||||
|
float milliseconds = 0;
|
||||||
|
cudaCheck(cudaEventElapsedTime(&milliseconds, begin, end), "measure kernel");
|
||||||
|
cudaEventDestroy(begin);
|
||||||
|
cudaEventDestroy(end);
|
||||||
|
|
||||||
|
uint32_t total = 0;
|
||||||
|
cudaCheck(cudaMemcpy(&total, deviceCount, sizeof(total), cudaMemcpyDeviceToHost), "copy count");
|
||||||
|
std::vector<Hit> hits(std::min(total, args.maxHits));
|
||||||
|
if (!hits.empty()) {
|
||||||
|
cudaCheck(cudaMemcpy(hits.data(), deviceHits, hits.size() * sizeof(Hit), cudaMemcpyDeviceToHost),
|
||||||
|
"copy hits");
|
||||||
|
}
|
||||||
|
std::sort(hits.begin(), hits.end(), [](const Hit& a, const Hit& b) {
|
||||||
|
return a.x < b.x || (a.x == b.x && a.z < b.z);
|
||||||
|
});
|
||||||
|
return RunResult{milliseconds, total, std::move(hits)};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
try {
|
||||||
|
const Args args = parseArgs(argc, argv);
|
||||||
|
Hit* deviceHits = nullptr;
|
||||||
|
uint32_t* deviceCount = nullptr;
|
||||||
|
cudaCheck(cudaMalloc(&deviceHits, args.maxHits * sizeof(Hit)), "allocate hits");
|
||||||
|
cudaCheck(cudaMalloc(&deviceCount, sizeof(uint32_t)), "allocate count");
|
||||||
|
|
||||||
|
// Warm up the context before timing.
|
||||||
|
cudaCheck(cudaFree(nullptr), "initialize CUDA");
|
||||||
|
std::vector<float> directTimes, outerTimes;
|
||||||
|
RunResult direct{}, outer{};
|
||||||
|
for (int repeat = 0; repeat < args.repeats; ++repeat) {
|
||||||
|
direct = run(args, Mode::Direct, deviceHits, deviceCount);
|
||||||
|
outer = run(args, Mode::Outer, deviceHits, deviceCount);
|
||||||
|
if (direct.totalHits != outer.totalHits || direct.storedHits != outer.storedHits) {
|
||||||
|
throw std::runtime_error("direct and outer-sum results disagree");
|
||||||
|
}
|
||||||
|
directTimes.push_back(direct.milliseconds);
|
||||||
|
outerTimes.push_back(outer.milliseconds);
|
||||||
|
}
|
||||||
|
cudaFree(deviceCount);
|
||||||
|
cudaFree(deviceHits);
|
||||||
|
std::sort(directTimes.begin(), directTimes.end());
|
||||||
|
std::sort(outerTimes.begin(), outerTimes.end());
|
||||||
|
const float directMs = directTimes[directTimes.size() / 2];
|
||||||
|
const float outerMs = outerTimes[outerTimes.size() / 2];
|
||||||
|
const uint64_t positions = uint64_t(int64_t(args.x1) - args.x0 + 1) *
|
||||||
|
uint64_t(int64_t(args.z1) - args.z0 + 1);
|
||||||
|
std::cout << "positions=" << positions << " size=" << args.size
|
||||||
|
<< " hits=" << direct.totalHits << "\n"
|
||||||
|
<< "direct_kernel_ms=" << directMs << " positions_per_second="
|
||||||
|
<< positions / (directMs * 1e-3) << "\n"
|
||||||
|
<< "outer_kernel_ms=" << outerMs << " positions_per_second="
|
||||||
|
<< positions / (outerMs * 1e-3) << "\n"
|
||||||
|
<< "outer_speedup=" << directMs / outerMs << "\n";
|
||||||
|
for (const Hit& hit : direct.storedHits) {
|
||||||
|
std::cout << "hit " << hit.x << ' ' << hit.z << "\n";
|
||||||
|
}
|
||||||
|
if (direct.totalHits > direct.storedHits.size()) {
|
||||||
|
std::cout << "truncated_hits=" << direct.totalHits - direct.storedHits.size() << "\n";
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
std::cerr << "error: " << error.what() << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue