Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
277 lines (262 loc) · 10.9 KB
/
Copy pathpyproject.toml
File metadata and controls
277 lines (262 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
[project]
name = "saccade"
version = "0.1.0"
description = "High-efficiency edge AI video perception system"
readme = "README.md"
requires-python = ">=3.12, <3.13"
# The distribution contains src/saccade/perception/eval/_torch_graphs.py, which
# is vendored from PyTorch under BSD-3-Clause, so PEP 639 wants it in the
# expression with its license text shipped alongside. third_party/TrackEval
# (MIT) is deliberately absent: it sits outside src/, no MANIFEST.in pulls it
# in, and `python -m build --sdist` confirms it ships zero files -- adding MIT
# here would claim license coverage for content the distribution does not have.
license = "Apache-2.0 AND BSD-3-Clause"
license-files = [
"LICENSE",
"NOTICE",
"src/saccade/perception/eval/_torch_graphs.LICENSE",
]
dependencies = [
# Tracker core: the module-level import closure of the public tracker
# surface (saccade.GPUByteTracker / saccade.ReorderingBuffer, i.e.
# perception/tracking, perception/box_ops, perception/eval/cuda_capture,
# paths). Everything else the package can do lives in an extra below;
# tests/contract/test_package_dependency_surface.py derives that closure
# from the sources and holds this list to it.
"numpy",
"torch==2.11.0",
# tensorrt-cu12 is a load-time dependency of the tracker extension, not
# only of the detector: saccade_tracking_ext has libnvinfer.so.10 as a
# direct NEEDED (through the saccade_perception static library) and
# resolves it via RUNPATH into this venv's tensorrt_libs, so `import
# saccade_tracking_ext` fails without it even though nothing under
# src/saccade imports the Python bindings on the tracker path. The
# detector path (detector_trt, feature_extractor, calibrator,
# mamba_gated_detector) imports the bindings directly. ADR 025 keeps it
# here for that reason; the C++/CUDA build toolchain is the `native-build`
# extra below.
"tensorrt-cu12==10.16.1.11",
]
# Each extra is a dependency boundary drawn around a part of the package, named
# by what that part does; the contract test maps every module to the extra
# that must cover its imports. Extras compose through self-references
# (`saccade[storage]`), so `pip install saccade[serve]` is complete on its own.
# The `dev` group below unions all of them for the repository workflow.
[project.optional-dependencies]
# Build toolchain for the C++/CUDA extension (ADR 025: the extension is built
# by the consumer, from a source checkout, against the venv it will be loaded
# in -- `cmake -S <checkout> -B <build> -DPYTHON_EXECUTABLE=<venv>/bin/python`;
# see docs/reference/runbooks/native_extension_install.md). Nothing under
# src/saccade imports any of these; they are consumed by CMakeLists.txt, which
# fails closed unless nvcc comes from this venv's nvidia/cu13 tree.
#
# H0 substrate: frozen build-CUDA toolchain (issue #214). The extension must
# compile against the same venv cu13 tree the uv.lock runtime resolves to, not
# the rolling system /opt/cuda. nvcc/nvvm/crt move as one matched line; 13.3
# (not the 13.0.x line of the torch-pinned runtime libs) because the 13.0 crt
# headers reject glibc >= 2.42 (C23 rsqrt collision). Compiling with a newer
# nvcc against the older pinned cudart/cublas is the supported direction and
# is verified by the extension_load qualification step. cccl pairs with nvcc
# (13.3); its exact nvcc==CUDART gate is disabled in CMakeLists because the
# runtime line is deliberately older (torch-pinned).
native-build = [
"nvidia-cuda-nvcc==13.3.73",
"nvidia-nvvm==13.3.73",
"nvidia-cuda-crt==13.3.73",
"nvidia-cuda-cccl==13.3.3.4.1",
"pybind11>=3.0.3",
]
# GPU media/NVDEC path only (media/dali_pipeline, media/rtsp_dali_pipeline,
# perception/eval/streaming). Cloud runners / C++ core / default uv sync skip
# this (large CUDA wheel; useless without GPU). Local GPU hosts:
# `uv sync --extra dali`.
dali = [
"nvidia-dali-cuda120>=2.0.0",
]
# Detection backend (perception/temporal_yolo, detector_trt, feature_extractor,
# cropper, calibrator). ultralytics is AGPL-3.0 and, per ADR 023 follow-up 3,
# no longer part of the default install: the TRT-backbone headline path does
# not import it, but the .pt loader, the training loss/assigner and the
# official-baseline scripts do. lap is ultralytics' tracker solver, which it
# otherwise auto-installs at first use.
yolo = [
"ultralytics",
"torchvision==0.26.0",
"lap>=0.5.13",
"opencv-python",
]
# Media ingest (media/*, perception/zero_copy): GStreamer via PyGObject, which
# needs the system girepository/cairo headers to build.
media = [
"pygobject==3.48.2",
"opencv-python",
]
# Persistence (storage/*). sqlalchemy/psycopg2 have no in-tree importer since
# AblationStore was removed (c5137317); docker-compose still provisions the
# postgres they were added for, so they stay declared here rather than dropped.
storage = [
"chromadb",
"redis",
"sqlalchemy>=2.0.38",
"psycopg2-binary>=2.9.10",
]
# The running service (api/*, pipeline/*, resource/*, perception/dispatcher,
# perception/entropy, main.py): HTTP surface, GPU health, ingest + persistence.
serve = [
"saccade[media,storage]",
"fastapi",
"uvicorn",
"pydantic",
"nvidia-ml-py",
"python-dotenv",
]
# LLM-side reasoning (cognition/*) over the same persistence layer; the
# orchestrator also reads GPU health (pipeline/health, resource/*), hence
# nvidia-ml-py without the rest of `serve`.
cognition = [
"saccade[storage]",
"llama-index>=0.14.21",
"llama-index-vector-stores-chroma>=0.5.5",
"python-dotenv",
"nvidia-ml-py",
]
# Benchmark / evaluation (perception/eval/*, scripts/eval/*): metrics, the
# offline analysis stack, and sliced-inference / logging tooling. pandas,
# scikit-learn, pyarrow and pillow are imported directly and were previously
# only present transitively (mlflow, motmetrics, torchvision); pycocotools and
# tabulate have no in-tree importer (added with c3b4384b) and are kept here
# pending a decision to drop them.
eval = [
"saccade[yolo]",
"motmetrics>=1.4.0",
"scipy",
"pandas",
"scikit-learn",
"pyarrow",
"pillow",
"sahi>=0.11.36",
"mlflow>=3.2.0",
"pyyaml>=6.0.3",
"pycocotools>=2.0.11",
"tabulate>=0.10.0",
]
# Training and model export (scripts/train/*, scripts/model/*,
# perception/text_encoder): backbones, ReID fine-tuning, ONNX export and
# quantisation. accelerate, einops and onnxscript have no in-tree importer
# (added with 9f448381 / 027c76fc) and are kept here pending a decision to
# drop them.
train = [
"saccade[yolo]",
"timm>=1.0.26",
"transformers>=4.48.0",
"accelerate>=1.13.0",
"optimum[onnxruntime-gpu]>=2.1.0",
"onnxruntime-gpu>=1.24.4",
"onnxscript>=0.6.2",
"einops>=0.8.2",
]
# Development-only tooling (PEP 735). `uv sync` installs this group by default,
# so pre_push.sh / CI keep working unchanged; `uv sync --no-dev` and a plain
# `pip install saccade` get the runtime set only. The group is the union of
# every extra except `dali` (GPU-only, stays an explicit `--extra dali`) plus
# the checkers: the extras are the consumer's dependency boundaries, the dev
# group is the repository's, and the two are different axes. Nothing under
# src/saccade imports the tooling directly; httpx/anyio may still appear
# transitively through runtime or test dependencies (fastapi, pytest plugins,
# TestClient), which is why this list removes only the direct requirement.
[dependency-groups]
dev = [
"saccade[native-build,yolo,media,storage,serve,cognition,eval,train]",
"ruff",
"pytest",
"httpx",
"mypy>=1.20.0",
"anyio>=4.13.0",
"pytest-asyncio>=1.3.0",
"pytest-anyio>=0.0.0",
"types-pyyaml>=6.0.12.20260408",
"pytest-cov>=6.0.0",
]
[build-system]
requires = ["setuptools>=77.0.3"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
package-dir = {"" = "src"}
# src/ also holds the C++/CUDA sources (src/{media,perception,tracking}) and,
# under the package, path-loaded research artefacts (perception/eval/_cuda) and
# runtime data (storage/chroma_db). With pyproject-style discovery setuptools
# defaults to namespaces = true and would ship all of those as packages, so
# discovery is pinned to regular packages rooted at `saccade`.
[tool.setuptools.packages.find]
where = ["src"]
include = ["saccade", "saccade.*"]
namespaces = false
[tool.uv]
override-dependencies = [
"onnxruntime==1.24.4",
]
[tool.mypy]
strict = true
ignore_missing_imports = true
exclude = ["tests/", "build/", "build-native-coverage/", "build-native-test/", "build-noopencv/", "build-release/", "storage/", "runs/", "results/", "out/", "train/", "scratch/", "third_party/"]
mypy_path = "src"
[[tool.mypy.overrides]]
module = ["scripts.*"]
ignore_errors = true
[[tool.mypy.overrides]]
module = ["saccade.perception.temporal_yolo.*"]
ignore_errors = true
# A transcription of torch.cuda.make_graphed_callables, kept byte-comparable to
# upstream by tests/unit/eval/test_torch_graphs_port.py -- which is also what
# stops anyone editing it to satisfy our stricter settings. Upstream is not
# written to `strict`, so the three errors it raises here (two untyped torch
# calls and one list/tuple argument) are upstream's, and silencing them in place
# would put our text and torch's out of sync for no behavioural gain.
[[tool.mypy.overrides]]
module = ["saccade.perception.eval._torch_graphs"]
ignore_errors = true
[[tool.mypy.overrides]]
module = [
"build_fpn_reid",
"saccade.media.dali_pipeline",
"saccade.media.rtsp_dali_pipeline",
"saccade.perception.detector_trt",
"saccade.perception.eval.detection",
"saccade.perception.eval.streaming",
"saccade.perception.tracking.fpn_reid",
"saccade.perception.tracking.fpn_reid_cuda",
"saccade.perception.workbench",
]
ignore_errors = true
[tool.ruff]
# Sealed research evidence is inventoried byte-exactly by each archive's own
# checksum manifest. A measurement child runs under a closed HOME/TMPDIR inside
# its own run directory, so Torch Inductor materialises generated `.py` kernels
# that are inventoried archive members. Formatting them rewrites sealed bytes in
# place and breaks the archive's own completeness check, so the evidence root is
# never source for lint or format purposes.
exclude = [
"tests/benchmarks/archive",
"storage/",
"runs/",
"results/",
"third_party/",
"scratch/",
"docs/modules/semantic/research/evidence/",
]
[tool.ruff.lint]
ignore = ["E701"]
[tool.ruff.lint.per-file-ignores]
"scripts/**" = ["E402"]
[tool.pytest.ini_options]
addopts = "-m 'not research'"
testpaths = ["tests"]
python_files = ["test_*.py"]
asyncio_mode = "strict"
pythonpath = ["src"]
markers = [
"gpu: tests that require a CUDA device or GPU-only native extension path",
"dali: tests that require a CUDA device and nvidia-dali (NVDEC decode path)",
"research: dated research / study-scoped tests under tests/research, excluded from the default collection (run with -m research)",
"packet_bound: reads a sealed research evidence packet or frozen dated artifact",
]