Ericzhang25 commited on
Commit
03e6843
·
verified ·
1 Parent(s): ba3a0fb

apptainer files for CryoZeta

Browse files
.gitattributes CHANGED
@@ -36,3 +36,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
36
  components.v20240608.cif filter=lfs diff=lfs merge=lfs -text
37
  msa/prot/24ee4eb8f30911b98ae173d19225440514a110ba50da1d89327c9c74ca0ac7fe/uniref100_hits.a3m filter=lfs diff=lfs merge=lfs -text
38
  examples/msa/prot/24ee4eb8f30911b98ae173d19225440514a110ba50da1d89327c9c74ca0ac7fe/uniref100_hits.a3m filter=lfs diff=lfs merge=lfs -text
 
 
36
  components.v20240608.cif filter=lfs diff=lfs merge=lfs -text
37
  msa/prot/24ee4eb8f30911b98ae173d19225440514a110ba50da1d89327c9c74ca0ac7fe/uniref100_hits.a3m filter=lfs diff=lfs merge=lfs -text
38
  examples/msa/prot/24ee4eb8f30911b98ae173d19225440514a110ba50da1d89327c9c74ca0ac7fe/uniref100_hits.a3m filter=lfs diff=lfs merge=lfs -text
39
+ apptainer/CryoZeta.sif filter=lfs diff=lfs merge=lfs -text
apptainer/CryoZeta.sif ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bff5f716227db0b7da90f29ae9526216d6ef595a66d729f4e4d98b73eb9b14d3
3
+ size 9623916544
apptainer/run_cryozeta.sh ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # ════════════════════════════════════════════════════════════════════
5
+ # CONFIGURE BEFORE RUNNING (assets_dir is a required CLI argument)
6
+ SIF="" # e.g. /path/to/CryoZeta.sif
7
+ # ════════════════════════════════════════════════════════════════════
8
+
9
+ GPU=""
10
+
11
+ usage() {
12
+ cat <<EOF
13
+ Usage: $(basename "$0") --gpu ID [OPTIONS] <input_json> <output_dir> <assets_dir>
14
+ $(basename "$0") prepare-ui [--sif PATH]
15
+
16
+ Run the CryoZeta pipeline (detection → inference → combine) via Apptainer.
17
+
18
+ Environment (prepare-ui):
19
+ CRYOZETA_PREP_UI_PORT HTTP port when the image supports --port (default: 8501). Ignored for legacy images.
20
+
21
+ Subcommands:
22
+ prepare-ui Launch the web UI for preparing input JSON files
23
+
24
+ Arguments:
25
+ input_json Path to input JSON file (e.g. examples/example.json)
26
+ output_dir Directory for output results
27
+ assets_dir Host directory with .safetensors weights (mounted as /app/CryoZeta/assets)
28
+
29
+ Required:
30
+ --gpu ID CUDA device ID (e.g. 0, 1, 2)
31
+
32
+ Options:
33
+ --sif PATH Path to CryoZeta.sif (default: $SIF)
34
+ --interpolation Use interpolation model in Stage 2
35
+ --seeds SEEDS Random seeds (default: 101)
36
+ --n-sample N Number of diffusion samples (default: 5)
37
+ --n-step N Number of diffusion steps (default: 20)
38
+ --n-cycle N Number of model cycles (default: 10)
39
+ --num-select N Number to select in combine (default: 5)
40
+ --skip-detection Skip Stage 1
41
+ --skip-inference Skip Stage 2
42
+ --skip-combine Skip Stage 3
43
+ -h, --help Show this help
44
+
45
+ Weights (not in the SIF): download into a directory and pass that path as <assets_dir>, e.g.
46
+ huggingface-cli download KiharaLab/CryoZeta --repo-type model --local-dir /path/to/assets
47
+ (pip install -U huggingface_hub). Stage 1: cryozeta-detection-v0.0.1.safetensors. Stage 2:
48
+ cryozeta-v0.0.1.safetensors, or cryozeta-interpolate-v0.0.1.safetensors if --interpolation.
49
+ EOF
50
+ exit 0
51
+ }
52
+
53
+ require_weight_file() {
54
+ local path=$1
55
+ local label=$2
56
+ if [[ ! -f "$path" ]]; then
57
+ echo "Error: missing $label: $path" >&2
58
+ echo "Download model files with Hugging Face CLI:" >&2
59
+ echo " huggingface-cli download KiharaLab/CryoZeta --repo-type model --local-dir <assets-dir>" >&2
60
+ echo " (Install CLI: pip install -U huggingface_hub)" >&2
61
+ echo "Then pass that directory as the <assets_dir> argument (same path as --local-dir)." >&2
62
+ exit 1
63
+ fi
64
+ }
65
+
66
+ # ── Defaults ─────────────────────────────────────────────────────────
67
+ USE_INTERPOLATION=false
68
+ SEEDS=101
69
+ N_SAMPLE=5
70
+ N_STEP=20
71
+ N_CYCLE=10
72
+ NUM_SELECT=5
73
+ SKIP_DETECTION=false
74
+ SKIP_INFERENCE=false
75
+ SKIP_COMBINE=false
76
+
77
+ # ── Handle prepare-ui subcommand ─────────────────────────────────────
78
+ if [[ "${1:-}" == "prepare-ui" ]]; then
79
+ shift
80
+ while [[ $# -gt 0 ]]; do
81
+ case $1 in
82
+ --sif) SIF="$2"; shift 2 ;;
83
+ -h|--help) usage ;;
84
+ *) echo "Unknown option: $1" >&2; exit 1 ;;
85
+ esac
86
+ done
87
+ [[ -f "$SIF" ]] || { echo "Error: SIF not found: $SIF" >&2; exit 1; }
88
+
89
+ set +e
90
+ _ui_help_msg="$(apptainer exec --bind "$(pwd):$(pwd)" "$SIF" bash -c \
91
+ "cd /app/CryoZeta && source /opt/conda/etc/profile.d/conda.sh && conda activate cryozeta && cryozeta-prepare ui --help" 2>&1)"
92
+ _ui_help_ec=$?
93
+ set -e
94
+
95
+ UI_SUPPORTS_PORT=false
96
+ if [[ "$_ui_help_ec" -eq 0 ]] && printf '%s\n' "$_ui_help_msg" | grep -qE '\-\-port([[:space:]=]|$)'; then
97
+ UI_SUPPORTS_PORT=true
98
+ else
99
+ echo "prepare-ui: could not confirm --port from 'cryozeta-prepare ui --help' (exit ${_ui_help_ec}); using legacy invocation without --port." >&2
100
+ fi
101
+
102
+ if [[ "$UI_SUPPORTS_PORT" != true ]]; then
103
+ echo "prepare-ui: http://localhost:8501 (default)" >&2
104
+ echo "prepare-ui: SSH tunnel: ssh -L 8501:localhost:8501 <user>@<host>" >&2
105
+ exec apptainer exec --bind "$(pwd):$(pwd)" "$SIF" bash -c \
106
+ "cd /app/CryoZeta && source /opt/conda/etc/profile.d/conda.sh && conda activate cryozeta && cryozeta-prepare ui"
107
+ fi
108
+
109
+ prep_ui_port="${CRYOZETA_PREP_UI_PORT:-8501}"
110
+ if ! [[ "$prep_ui_port" =~ ^[0-9]+$ ]] || (( prep_ui_port > 65535 )); then
111
+ echo "Error: CRYOZETA_PREP_UI_PORT must be an integer 0–65535" >&2
112
+ exit 1
113
+ fi
114
+
115
+ echo "prepare-ui: http://localhost:${prep_ui_port}" >&2
116
+ echo "prepare-ui: SSH tunnel: ssh -L ${prep_ui_port}:localhost:${prep_ui_port} <user>@<host>" >&2
117
+ exec apptainer exec --bind "$(pwd):$(pwd)" "$SIF" bash -c \
118
+ "cd /app/CryoZeta && source /opt/conda/etc/profile.d/conda.sh && conda activate cryozeta && cryozeta-prepare ui --port ${prep_ui_port}"
119
+ fi
120
+
121
+ # ── Parse arguments ──────────────────────────────────────────────────
122
+ POSITIONAL=()
123
+ while [[ $# -gt 0 ]]; do
124
+ case $1 in
125
+ --sif) SIF="$2"; shift 2 ;;
126
+ --gpu) GPU="$2"; shift 2 ;;
127
+ --interpolation) USE_INTERPOLATION=true; shift ;;
128
+ --seeds) SEEDS="$2"; shift 2 ;;
129
+ --n-sample) N_SAMPLE="$2"; shift 2 ;;
130
+ --n-step) N_STEP="$2"; shift 2 ;;
131
+ --n-cycle) N_CYCLE="$2"; shift 2 ;;
132
+ --num-select) NUM_SELECT="$2"; shift 2 ;;
133
+ --skip-detection) SKIP_DETECTION=true; shift ;;
134
+ --skip-inference) SKIP_INFERENCE=true; shift ;;
135
+ --skip-combine) SKIP_COMBINE=true; shift ;;
136
+ -h|--help) usage ;;
137
+ -*) echo "Unknown option: $1" >&2; exit 1 ;;
138
+ *) POSITIONAL+=("$1"); shift ;;
139
+ esac
140
+ done
141
+
142
+ if [[ ${#POSITIONAL[@]} -ne 3 ]]; then
143
+ echo "Error: expected 3 positional arguments (<input_json> <output_dir> <assets_dir>), got ${#POSITIONAL[@]}" >&2
144
+ usage
145
+ fi
146
+
147
+ INPUT_JSON="$(realpath "${POSITIONAL[0]}")"
148
+ OUTPUT_DIR="$(realpath -m "${POSITIONAL[1]}")"
149
+ ASSETS="$(realpath "${POSITIONAL[2]}")"
150
+
151
+ # ── Validate ─────────────────────────────────────────────────────────
152
+ [[ -n "$GPU" ]] || { echo "Error: --gpu is required" >&2; usage; }
153
+ [[ -n "$SIF" ]] || { echo "Error: SIF not set — edit top of script or pass --sif PATH" >&2; exit 1; }
154
+ [[ -f "$SIF" ]] || { echo "Error: SIF not found: $SIF" >&2; exit 1; }
155
+ [[ -f "$INPUT_JSON" ]] || { echo "Error: input JSON not found: $INPUT_JSON" >&2; exit 1; }
156
+ [[ -d "$ASSETS" ]] || { echo "Error: assets dir not found: $ASSETS" >&2; exit 1; }
157
+ mkdir -p "$OUTPUT_DIR"
158
+
159
+ BIND_FLAGS=("--bind" "$(pwd):$(pwd)" "--bind" "$ASSETS:/app/CryoZeta/assets")
160
+
161
+ if [[ "$SKIP_DETECTION" == false ]]; then
162
+ require_weight_file "$ASSETS/cryozeta-detection-v0.0.1.safetensors" "detection weights (Stage 1)"
163
+ fi
164
+ if [[ "$SKIP_INFERENCE" == false ]]; then
165
+ if [[ "$USE_INTERPOLATION" == true ]]; then
166
+ require_weight_file "$ASSETS/cryozeta-interpolate-v0.0.1.safetensors" "interpolation checkpoint (Stage 2)"
167
+ else
168
+ require_weight_file "$ASSETS/cryozeta-v0.0.1.safetensors" "structure-prediction checkpoint (Stage 2)"
169
+ fi
170
+ fi
171
+
172
+ echo "Bind mounts: ${BIND_FLAGS[*]}"
173
+
174
+ # ── GPU capability detection ──────────────────────────────────────────
175
+ # nvidia-smi -i GPU uses PCI bus order, matching CUDA_DEVICE_ORDER=PCI_BUS_ID
176
+ DRIVER_MAJOR=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader -i "$GPU" 2>/dev/null \
177
+ | head -1 | cut -d. -f1)
178
+ GPU_CC_RAW=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader -i "$GPU" 2>/dev/null \
179
+ | head -1 | tr -d '.')
180
+ DRIVER_MAJOR="${DRIVER_MAJOR:-0}"
181
+ GPU_CC_RAW="${GPU_CC_RAW:-75}"
182
+ echo "GPU $GPU: sm_${GPU_CC_RAW}, driver ${DRIVER_MAJOR}.x"
183
+
184
+ # DS4Sci EvoformerAttention requires sm_80+.
185
+ # CUDA 12.9 JIT custom kernels (fast_layernorm, DS4Sci) produce PTX ISA 8.9,
186
+ # which drivers older than 555 (CUDA 12.9 minimum) cannot assemble.
187
+ # Fall back to standard PyTorch ops on older hardware.
188
+ if [[ "$DRIVER_MAJOR" -ge 555 ]] && [[ "$GPU_CC_RAW" -ge 80 ]]; then
189
+ USE_DS_EVO_ATTN=true
190
+ USE_CUEQ_ATTN=true
191
+ USE_LMA=false
192
+ ATTN_CHUNK_SIZE=256
193
+ LAYERNORM_PREFIX="export LAYERNORM_TYPE=fast_layernorm && "
194
+ echo "GPU compat: sm_${GPU_CC_RAW} + driver ${DRIVER_MAJOR} — JIT custom kernels enabled"
195
+ else
196
+ # DS4Sci EvoformerAttention requires sm_80+.
197
+ # cuEquivariance falls back to DS4Sci when shape limits are hit, so must
198
+ # also be disabled — otherwise primitives.py forces use_deepspeed_evo_attention=True.
199
+ # CUDA 12.9 JIT (fast_layernorm, DS4Sci) produces PTX ISA 8.9 which
200
+ # drivers older than 555 cannot assemble.
201
+ # LMA (Low Memory Attention) is used instead — chunks Q in 1024-token steps to
202
+ # avoid the O(N²) memory spike that OOMs with standard attention on N=1313.
203
+ # DS4Sci EvoformerAttention requires sm_80+.
204
+ # cuEquivariance falls back to DS4Sci when shape limits are hit, so must
205
+ # also be disabled — otherwise primitives.py forces use_deepspeed_evo_attention=True.
206
+ # CUDA 12.9 JIT (fast_layernorm, DS4Sci) produces PTX ISA 8.9 which
207
+ # drivers older than 555 cannot assemble.
208
+ # LMA (Low Memory Attention) is used instead — chunks Q in 1024-token steps.
209
+ # Default chunk_size=256 gives a=[1,256,8,1024,1313]=5.4GB tensor → OOM.
210
+ # Reduce to 32: a=[1,32,8,1024,1313]=~684MB, safely fits in reserved pool.
211
+ USE_DS_EVO_ATTN=false
212
+ USE_CUEQ_ATTN=false
213
+ USE_LMA=true
214
+ ATTN_CHUNK_SIZE=32
215
+ LAYERNORM_PREFIX=""
216
+ echo "GPU compat: sm_${GPU_CC_RAW} + driver ${DRIVER_MAJOR} — JIT custom kernels disabled (LMA, chunk_size=${ATTN_CHUNK_SIZE})"
217
+ fi
218
+
219
+ # Helper: run a command inside the container
220
+ run_in_container() {
221
+ local need_gpu="$1"; shift
222
+ # Cache + Triton ptxas: match CryoZeta/env_setup.sh; \${…} expands inside the container.
223
+ # ptxas-blackwell shim lives under CRYOZETA_CACHE_DIR (same tree as Triton/Torch caches).
224
+ local cmd
225
+ cmd="cd /app/CryoZeta && source /opt/conda/etc/profile.d/conda.sh && conda activate cryozeta && "
226
+ cmd+="_CRYOZETA_CACHE=\"\${CRYOZETA_CACHE_DIR:-\${HOME}/.cache/cryozeta}\" && "
227
+ cmd+="export CRYOZETA_CACHE_DIR=\"\${_CRYOZETA_CACHE}\" && "
228
+ cmd+="export TORCH_EXTENSIONS_DIR=\"\${TORCH_EXTENSIONS_DIR:-\${_CRYOZETA_CACHE}/torch_extensions}\" && "
229
+ cmd+="export TORCHINDUCTOR_CACHE_DIR=\"\${TORCHINDUCTOR_CACHE_DIR:-\${_CRYOZETA_CACHE}/inductor}\" && "
230
+ cmd+="export TRITON_CACHE_DIR=\"\${TRITON_CACHE_DIR:-\${_CRYOZETA_CACHE}/triton}\" && "
231
+ cmd+="unset _CRYOZETA_CACHE && "
232
+ cmd+="[ -x \"\${CONDA_PREFIX}/bin/ptxas\" ] && export TRITON_PTXAS_PATH=\"\${CONDA_PREFIX}/bin/ptxas\" TRITON_PTXAS_BLACKWELL_PATH=\"\${CONDA_PREFIX}/bin/ptxas\" ; "
233
+ cmd+="mkdir -p \"\${CRYOZETA_CACHE_DIR}/triton-bin\" && ln -sf \"\${CONDA_PREFIX}/bin/ptxas\" \"\${CRYOZETA_CACHE_DIR}/triton-bin/ptxas-blackwell\" && export PATH=\"\${CRYOZETA_CACHE_DIR}/triton-bin:\$PATH\" && $*"
234
+
235
+ if [[ "$need_gpu" == "gpu" ]]; then
236
+ CUDA_VISIBLE_DEVICES="$GPU" apptainer exec --nv \
237
+ "${BIND_FLAGS[@]}" \
238
+ "$SIF" bash -c "$cmd"
239
+ else
240
+ apptainer exec \
241
+ "${BIND_FLAGS[@]}" \
242
+ "$SIF" bash -c "$cmd"
243
+ fi
244
+ }
245
+
246
+ # Pick checkpoint based on interpolation flag
247
+ if [[ "$USE_INTERPOLATION" == true ]]; then
248
+ CHECKPOINT="/app/CryoZeta/assets/cryozeta-interpolate-v0.0.1.safetensors"
249
+ else
250
+ CHECKPOINT="/app/CryoZeta/assets/cryozeta-v0.0.1.safetensors"
251
+ fi
252
+
253
+ # ── Stage 1: Atom Detection ─────────────────────────────────────────
254
+ if [[ "$SKIP_DETECTION" == false ]]; then
255
+ echo "=== Stage 1: Atom Detection ==="
256
+ run_in_container gpu \
257
+ cryozeta-detection json-run "$INPUT_JSON" "$OUTPUT_DIR" --device cuda
258
+ fi
259
+
260
+ # ── Stage 2: Structure Prediction ────────────────────────────────────
261
+ if [[ "$SKIP_INFERENCE" == false ]]; then
262
+ echo "=== Stage 2: Structure Prediction (interpolation=$USE_INTERPOLATION) ==="
263
+ run_in_container gpu \
264
+ "${LAYERNORM_PREFIX}cryozeta-inference \
265
+ --seeds $SEEDS \
266
+ --load_checkpoint_path $CHECKPOINT \
267
+ --em_file_dir $OUTPUT_DIR \
268
+ --dump_dir $OUTPUT_DIR \
269
+ --input_json_path $INPUT_JSON \
270
+ --use_deepspeed_evo_attention $USE_DS_EVO_ATTN \
271
+ --use_cuequivariance_attention $USE_CUEQ_ATTN \
272
+ --use_lma $USE_LMA \
273
+ --infer_setting.chunk_size $ATTN_CHUNK_SIZE \
274
+ --model.N_cycle $N_CYCLE \
275
+ --sample_diffusion.N_sample $N_SAMPLE \
276
+ --sample_diffusion.N_step $N_STEP \
277
+ --data.num_dl_workers 1 \
278
+ --use_interpolation $USE_INTERPOLATION \
279
+ --overwrite false"
280
+ fi
281
+
282
+ # ── Stage 3: Combine Results ─────────────────────────────────────────
283
+ if [[ "$SKIP_COMBINE" == false ]]; then
284
+ echo "=== Stage 3: Combine Results ==="
285
+ run_in_container cpu \
286
+ cryozeta-combine \
287
+ --dump-dir "$OUTPUT_DIR" \
288
+ --input-json-path "$INPUT_JSON" \
289
+ --seeds "$SEEDS" \
290
+ --num-select "$NUM_SELECT"
291
+ fi
292
+
293
+ echo "=== Done. Results in: $OUTPUT_DIR ==="
apptainer/run_cryozeta_large.sh ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # ════════════════════════════════════════════════════════════════════
5
+ # CONFIGURE BEFORE RUNNING (assets_dir is a required CLI argument)
6
+ SIF="" # e.g. /path/to/CryoZeta.sif
7
+ # ════════════════════════════════════════════════════════════════════
8
+
9
+ GPU=""
10
+
11
+ usage() {
12
+ cat <<EOF
13
+ Usage: $(basename "$0") --gpu IDS [OPTIONS] <input_json> <output_dir> <assets_dir>
14
+
15
+ Run the CryoZeta large-complex cycle pipeline via Apptainer.
16
+
17
+ Arguments:
18
+ input_json Path to a large-example JSON list
19
+ output_dir Directory for output results
20
+ assets_dir Host directory with .safetensors weights (mounted as /app/CryoZeta/assets)
21
+
22
+ Required:
23
+ --gpu IDS CUDA device IDs (e.g. 0 or 0,1)
24
+
25
+ Options:
26
+ --sif PATH Path to CryoZeta.sif (default: $SIF)
27
+ --example SEL Select one JSON entry by 0-based index or name (default: 0)
28
+ --registration M Registration method: auto, teaser, svd, vesper (default: auto)
29
+ --threshold X EM point cropping threshold in Angstrom (default: 5.0)
30
+ --seeds SEEDS Random seeds (default: 101)
31
+ --n-sample N Number of diffusion samples (default: 5)
32
+ --n-step N Number of diffusion steps (default: 20)
33
+ --n-cycle N Number of model cycles (default: 10)
34
+ --use-opm-tilelang Enable TileLang OPM kernel
35
+ --skip-detection Skip detection and reuse existing .pt output
36
+ --skip-combine Skip combine-stages
37
+ --vesper-map PATH Original MRC map path for VESPER mode
38
+ --vesper-contour X Contour level for VESPER mode
39
+ --vesper-resolution X
40
+ Simulated-map resolution for VESPER mode (default: 3.0)
41
+ --vesper-angle-spacing X
42
+ Angular sampling interval for VESPER mode (default: 5.0)
43
+ -h, --help Show this help
44
+
45
+ Weights (not in the SIF): download into a directory and pass that path as <assets_dir>, e.g.
46
+ huggingface-cli download KiharaLab/CryoZeta --repo-type model --local-dir /path/to/assets
47
+ (pip install -U huggingface_hub). Required here:
48
+ cryozeta-detection-v0.0.1.safetensors and cryozeta-v0.0.1.safetensors
49
+ EOF
50
+ exit 0
51
+ }
52
+
53
+ require_weight_file() {
54
+ local path=$1
55
+ local label=$2
56
+ if [[ ! -f "$path" ]]; then
57
+ echo "Error: missing $label: $path" >&2
58
+ echo "Download model files with Hugging Face CLI:" >&2
59
+ echo " huggingface-cli download KiharaLab/CryoZeta --repo-type model --local-dir <assets-dir>" >&2
60
+ echo " (Install CLI: pip install -U huggingface_hub)" >&2
61
+ echo "Then pass that directory as the <assets_dir> argument (same path as --local-dir)." >&2
62
+ exit 1
63
+ fi
64
+ }
65
+
66
+ EXAMPLE_SEL="0"
67
+ REGISTRATION_METHOD="auto"
68
+ THRESHOLD="5.0"
69
+ SEEDS=101
70
+ N_SAMPLE=5
71
+ N_STEP=20
72
+ N_CYCLE=10
73
+ USE_OPM_TILELANG=false
74
+ SKIP_DETECTION=false
75
+ SKIP_COMBINE=false
76
+ VESPER_MAP_PATH=""
77
+ VESPER_CONTOUR_LEVEL=""
78
+ VESPER_RESOLUTION="3.0"
79
+ VESPER_ANGLE_SPACING="5.0"
80
+
81
+ POSITIONAL=()
82
+ while [[ $# -gt 0 ]]; do
83
+ case $1 in
84
+ --sif) SIF="$2"; shift 2 ;;
85
+ --gpu) GPU="$2"; shift 2 ;;
86
+ --example) EXAMPLE_SEL="$2"; shift 2 ;;
87
+ --registration) REGISTRATION_METHOD="$2"; shift 2 ;;
88
+ --threshold) THRESHOLD="$2"; shift 2 ;;
89
+ --seeds) SEEDS="$2"; shift 2 ;;
90
+ --n-sample) N_SAMPLE="$2"; shift 2 ;;
91
+ --n-step) N_STEP="$2"; shift 2 ;;
92
+ --n-cycle) N_CYCLE="$2"; shift 2 ;;
93
+ --use-opm-tilelang) USE_OPM_TILELANG=true; shift ;;
94
+ --skip-detection) SKIP_DETECTION=true; shift ;;
95
+ --skip-combine) SKIP_COMBINE=true; shift ;;
96
+ --vesper-map) VESPER_MAP_PATH="$2"; shift 2 ;;
97
+ --vesper-contour) VESPER_CONTOUR_LEVEL="$2"; shift 2 ;;
98
+ --vesper-resolution) VESPER_RESOLUTION="$2"; shift 2 ;;
99
+ --vesper-angle-spacing) VESPER_ANGLE_SPACING="$2"; shift 2 ;;
100
+ -h|--help) usage ;;
101
+ -*) echo "Unknown option: $1" >&2; exit 1 ;;
102
+ *) POSITIONAL+=("$1"); shift ;;
103
+ esac
104
+ done
105
+
106
+ if [[ ${#POSITIONAL[@]} -ne 3 ]]; then
107
+ echo "Error: expected 3 positional arguments (<input_json> <output_dir> <assets_dir>), got ${#POSITIONAL[@]}" >&2
108
+ usage
109
+ fi
110
+
111
+ INPUT_JSON="$(realpath "${POSITIONAL[0]}")"
112
+ OUTPUT_DIR="$(realpath -m "${POSITIONAL[1]}")"
113
+ ASSETS="$(realpath "${POSITIONAL[2]}")"
114
+ SELECTED_INPUT_JSON="$(realpath -m "${OUTPUT_DIR}/selected_input_entry.json")"
115
+ CYCLE_INPUT_JSON="$(realpath -m "${OUTPUT_DIR}/selected_cycle_input_entry.json")"
116
+ TRITON_BIN_DIR="$(realpath -m "$(pwd)/tmp/triton-bin")"
117
+
118
+ [[ -n "$GPU" ]] || { echo "Error: --gpu is required" >&2; usage; }
119
+ [[ -n "$SIF" ]] || { echo "Error: SIF not set — edit top of script or pass --sif PATH" >&2; exit 1; }
120
+ [[ -f "$SIF" ]] || { echo "Error: SIF not found: $SIF" >&2; exit 1; }
121
+ [[ -f "$INPUT_JSON" ]] || { echo "Error: input JSON not found: $INPUT_JSON" >&2; exit 1; }
122
+ [[ -d "$ASSETS" ]] || { echo "Error: assets dir not found: $ASSETS" >&2; exit 1; }
123
+
124
+ if [[ "$SKIP_DETECTION" == false ]]; then
125
+ require_weight_file "$ASSETS/cryozeta-detection-v0.0.1.safetensors" "detection weights (Stage 1)"
126
+ fi
127
+ require_weight_file "$ASSETS/cryozeta-v0.0.1.safetensors" "cycle-prediction checkpoint"
128
+
129
+ mkdir -p "$OUTPUT_DIR" "$TRITON_BIN_DIR"
130
+
131
+ python3 - "$INPUT_JSON" "$SELECTED_INPUT_JSON" "$CYCLE_INPUT_JSON" "$OUTPUT_DIR" "$EXAMPLE_SEL" <<'PY'
132
+ import json
133
+ import os
134
+ import sys
135
+ from pathlib import Path
136
+
137
+ input_json = Path(sys.argv[1]).resolve()
138
+ selected_input_json = Path(sys.argv[2]).resolve()
139
+ cycle_input_json = Path(sys.argv[3]).resolve()
140
+ dump_dir = Path(sys.argv[4]).resolve()
141
+ selection = sys.argv[5]
142
+ repo_root = Path.cwd().resolve()
143
+
144
+ data = json.loads(input_json.read_text())
145
+ if not isinstance(data, list) or len(data) == 0:
146
+ raise ValueError(f"Input JSON must be a non-empty list: {input_json}")
147
+
148
+ if selection.isdigit():
149
+ entry_index = int(selection)
150
+ if not (0 <= entry_index < len(data)):
151
+ raise IndexError(
152
+ f"example index {entry_index} is out of range for {len(data)} entries"
153
+ )
154
+ else:
155
+ matches = [i for i, item in enumerate(data) if item.get("name") == selection]
156
+ if not matches:
157
+ names = ", ".join(item.get("name", "<missing>") for item in data)
158
+ raise ValueError(
159
+ f"example '{selection}' not found in {input_json}. Available names: {names}"
160
+ )
161
+ entry_index = matches[0]
162
+
163
+ json_dir = input_json.parent
164
+
165
+ def resolve_path(value: str) -> str:
166
+ if not value:
167
+ return value
168
+ p = Path(value)
169
+ if p.is_absolute():
170
+ return str(p.resolve())
171
+
172
+ # First support repo-root-relative paths such as server/8zui/60484.map,
173
+ # which are common in server-generated JSON files.
174
+ repo_candidate = (repo_root / p).resolve()
175
+ if repo_candidate.exists():
176
+ return str(repo_candidate)
177
+
178
+ # Otherwise fall back to JSON-local relative resolution.
179
+ json_candidate = (json_dir / p).resolve()
180
+ return str(json_candidate)
181
+
182
+ def normalize_entry_paths(entry):
183
+ if "map_path" in entry and entry["map_path"]:
184
+ entry["map_path"] = resolve_path(entry["map_path"])
185
+ if "em_file" in entry and entry["em_file"]:
186
+ entry["em_file"] = resolve_path(entry["em_file"])
187
+ for seq in entry.get("sequences", []):
188
+ for kind in ("proteinChain", "dnaSequence", "rnaSequence"):
189
+ node = seq.get(kind, {})
190
+ msa = node.get("msa", {})
191
+ if msa.get("precomputed_msa_dir"):
192
+ msa["precomputed_msa_dir"] = resolve_path(msa["precomputed_msa_dir"])
193
+ lig = seq.get("ligand", {})
194
+ lig_value = lig.get("ligand")
195
+ if isinstance(lig_value, str) and lig_value.startswith("FILE_"):
196
+ lig["ligand"] = "FILE_" + resolve_path(lig_value[5:])
197
+ return entry
198
+
199
+ entry = normalize_entry_paths(dict(data[entry_index]))
200
+ name = entry.get("name")
201
+ if not name:
202
+ raise ValueError("Selected entry must contain 'name'")
203
+
204
+ selected_input_json.parent.mkdir(parents=True, exist_ok=True)
205
+ selected_input_json.write_text(json.dumps([entry], indent=2))
206
+
207
+ cycle_entry = dict(entry)
208
+ cycle_entry["em_file"] = str(
209
+ (dump_dir / name / "CryoZeta-Detection" / f"{name}.pt").resolve()
210
+ )
211
+ cycle_input_json.parent.mkdir(parents=True, exist_ok=True)
212
+ cycle_input_json.write_text(json.dumps([cycle_entry], indent=2))
213
+
214
+ print(f"Prepared sample: {name} (index {entry_index})")
215
+ print(f" detection input: {selected_input_json}")
216
+ print(f" cycle input: {cycle_input_json}")
217
+ PY
218
+
219
+ SELECTED_NAME="$(python3 - "$SELECTED_INPUT_JSON" <<'PY'
220
+ import json
221
+ import sys
222
+ with open(sys.argv[1]) as fh:
223
+ data = json.load(fh)
224
+ print(data[0]["name"])
225
+ PY
226
+ )"
227
+
228
+ BIND_FLAGS=(
229
+ "--bind" "$INPUT_JSON:$INPUT_JSON"
230
+ "--bind" "$SELECTED_INPUT_JSON:$SELECTED_INPUT_JSON"
231
+ "--bind" "$CYCLE_INPUT_JSON:$CYCLE_INPUT_JSON"
232
+ "--bind" "$OUTPUT_DIR:$OUTPUT_DIR"
233
+ "--bind" "$ASSETS:/app/CryoZeta/assets"
234
+ "--bind" "$(pwd):$(pwd)"
235
+ )
236
+
237
+ while IFS= read -r p; do
238
+ [[ -n "$p" ]] && [[ -e "$p" ]] && BIND_FLAGS+=("--bind" "$p:$p")
239
+ done < <(python3 - "$SELECTED_INPUT_JSON" "$VESPER_MAP_PATH" <<'PY'
240
+ import json
241
+ import os
242
+ import sys
243
+
244
+ selected_json = sys.argv[1]
245
+ vesper_map = sys.argv[2]
246
+
247
+ with open(selected_json) as f:
248
+ entries = json.load(f)
249
+
250
+ dirs = set()
251
+ for entry in entries:
252
+ for key in ("map_path", "em_file"):
253
+ path = entry.get(key, "")
254
+ if path:
255
+ dirs.add(os.path.dirname(os.path.abspath(path)))
256
+ for seq in entry.get("sequences", []):
257
+ for kind in ("proteinChain", "dnaSequence", "rnaSequence"):
258
+ msa_dir = seq.get(kind, {}).get("msa", {}).get("precomputed_msa_dir", "")
259
+ if msa_dir:
260
+ dirs.add(os.path.abspath(msa_dir))
261
+ lig = seq.get("ligand", {}).get("ligand", "")
262
+ if isinstance(lig, str) and lig.startswith("FILE_"):
263
+ dirs.add(os.path.dirname(os.path.abspath(lig[5:])))
264
+
265
+ if vesper_map:
266
+ dirs.add(os.path.dirname(os.path.abspath(vesper_map)))
267
+
268
+ for d in sorted(dirs):
269
+ print(d)
270
+ PY
271
+ )
272
+
273
+ echo "Bind mounts: ${BIND_FLAGS[*]}"
274
+
275
+ DRIVER_MAJOR=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader -i "${GPU%%,*}" 2>/dev/null \
276
+ | head -1 | cut -d. -f1)
277
+ GPU_CC_RAW=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader -i "${GPU%%,*}" 2>/dev/null \
278
+ | head -1 | tr -d '.')
279
+ DRIVER_MAJOR="${DRIVER_MAJOR:-0}"
280
+ GPU_CC_RAW="${GPU_CC_RAW:-75}"
281
+ echo "GPU ${GPU}: sm_${GPU_CC_RAW}, driver ${DRIVER_MAJOR}.x"
282
+
283
+ if [[ "$DRIVER_MAJOR" -ge 555 ]] && [[ "$GPU_CC_RAW" -ge 80 ]]; then
284
+ USE_DS_EVO_ATTN=true
285
+ USE_CUEQ_ATTN=true
286
+ LAYERNORM_PREFIX="export LAYERNORM_TYPE=fast_layernorm && "
287
+ echo "GPU compat: sm_${GPU_CC_RAW} + driver ${DRIVER_MAJOR} — JIT custom kernels enabled"
288
+ else
289
+ USE_DS_EVO_ATTN=false
290
+ USE_CUEQ_ATTN=false
291
+ LAYERNORM_PREFIX=""
292
+ echo "GPU compat: sm_${GPU_CC_RAW} + driver ${DRIVER_MAJOR} — JIT custom kernels disabled"
293
+ fi
294
+
295
+ run_in_container() {
296
+ local need_gpu="$1"; shift
297
+ local cmd
298
+ cmd="cd /app/CryoZeta && source /opt/conda/etc/profile.d/conda.sh && conda activate cryozeta && "
299
+ cmd+="_CRYOZETA_CACHE=\"\${CRYOZETA_CACHE_DIR:-\${HOME}/.cache/cryozeta}\" && "
300
+ cmd+="export CRYOZETA_CACHE_DIR=\"\${_CRYOZETA_CACHE}\" && "
301
+ cmd+="export TORCH_EXTENSIONS_DIR=\"\${TORCH_EXTENSIONS_DIR:-\${_CRYOZETA_CACHE}/torch_extensions}\" && "
302
+ cmd+="export TORCHINDUCTOR_CACHE_DIR=\"\${TORCHINDUCTOR_CACHE_DIR:-\${_CRYOZETA_CACHE}/inductor}\" && "
303
+ cmd+="export TRITON_CACHE_DIR=\"\${TRITON_CACHE_DIR:-\${_CRYOZETA_CACHE}/triton}\" && "
304
+ cmd+="export TMPDIR=\"\${TMPDIR:-\${_CRYOZETA_CACHE}/tmp}\" && "
305
+ cmd+="unset _CRYOZETA_CACHE && "
306
+ cmd+="mkdir -p \"\${CRYOZETA_CACHE_DIR}/triton-bin\" \"\${TORCH_EXTENSIONS_DIR}\" \"\${TORCHINDUCTOR_CACHE_DIR}\" \"\${TRITON_CACHE_DIR}\" \"\${TMPDIR}\" && "
307
+ cmd+="[ -x \"\${CONDA_PREFIX}/bin/ptxas\" ] && export TRITON_PTXAS_PATH=\"\${CONDA_PREFIX}/bin/ptxas\" TRITON_PTXAS_BLACKWELL_PATH=\"\${CONDA_PREFIX}/bin/ptxas\" ; "
308
+ cmd+="ln -sf \"\${CONDA_PREFIX}/bin/ptxas\" \"\${CRYOZETA_CACHE_DIR}/triton-bin/ptxas-blackwell\" && export PATH=\"\${CRYOZETA_CACHE_DIR}/triton-bin:\$PATH\" && $*"
309
+
310
+ if [[ "$need_gpu" == "gpu" ]]; then
311
+ CUDA_VISIBLE_DEVICES="$GPU" apptainer exec --nv \
312
+ "${BIND_FLAGS[@]}" \
313
+ "$SIF" bash -c "$cmd"
314
+ else
315
+ apptainer exec \
316
+ "${BIND_FLAGS[@]}" \
317
+ "$SIF" bash -c "$cmd"
318
+ fi
319
+ }
320
+
321
+ REGISTRATION_FLAG=""
322
+ case "$REGISTRATION_METHOD" in
323
+ auto)
324
+ echo "Registration method: auto (compare TEASER++ and SVD, or VESPER if requested via flags)"
325
+ ;;
326
+ teaser)
327
+ REGISTRATION_FLAG="--teaser_only"
328
+ echo "Registration method: TEASER++ only"
329
+ ;;
330
+ svd)
331
+ REGISTRATION_FLAG="--svd_only"
332
+ echo "Registration method: SVD only"
333
+ ;;
334
+ vesper)
335
+ REGISTRATION_FLAG="--vesper_only"
336
+ echo "Registration method: VESPER only"
337
+ ;;
338
+ *)
339
+ echo "Error: unknown registration method '$REGISTRATION_METHOD' (expected auto|teaser|svd|vesper)" >&2
340
+ exit 1
341
+ ;;
342
+ esac
343
+
344
+ VESPER_ARGS=()
345
+ if [[ -n "$VESPER_MAP_PATH" ]]; then
346
+ VESPER_MAP_PATH="$(realpath "$VESPER_MAP_PATH")"
347
+ VESPER_ARGS+=("--vesper_map_path" "$VESPER_MAP_PATH")
348
+ fi
349
+ if [[ -n "$VESPER_CONTOUR_LEVEL" ]]; then
350
+ VESPER_ARGS+=("--vesper_contour_level" "$VESPER_CONTOUR_LEVEL")
351
+ fi
352
+ VESPER_ARGS+=("--vesper_resolution" "$VESPER_RESOLUTION" "--vesper_angle_spacing" "$VESPER_ANGLE_SPACING")
353
+
354
+ echo "Selected sample: $SELECTED_NAME"
355
+ echo "Output directory: $OUTPUT_DIR"
356
+
357
+ if [[ "$SKIP_DETECTION" == false ]]; then
358
+ echo "=== Stage 1: Atom Detection ==="
359
+ run_in_container gpu \
360
+ cryozeta-detection json-run "$SELECTED_INPUT_JSON" "$OUTPUT_DIR" --device cuda --overwrite
361
+ fi
362
+
363
+ echo "=== Stage 2: Large-Complex Cycle Prediction ==="
364
+ run_in_container gpu \
365
+ "${LAYERNORM_PREFIX}cryozeta-cycle-predict \
366
+ --base_json $CYCLE_INPUT_JSON \
367
+ --load_checkpoint_path /app/CryoZeta/assets/cryozeta-v0.0.1.safetensors \
368
+ --dump_root $OUTPUT_DIR \
369
+ --threshold $THRESHOLD \
370
+ --seeds $SEEDS \
371
+ --sample.N_sample $N_SAMPLE \
372
+ --sample.N_step $N_STEP \
373
+ --model.N_cycle $N_CYCLE \
374
+ --use_deepspeed_evo_attention $USE_DS_EVO_ATTN \
375
+ --use_cuequivariance_attention $USE_CUEQ_ATTN \
376
+ --use_cuequivariance_multiplicative_update $USE_CUEQ_ATTN \
377
+ --use_cuequivariance_attention_pair_bias false \
378
+ --use_opm_tilelang $USE_OPM_TILELANG \
379
+ --num_dl_workers 1 \
380
+ ${REGISTRATION_FLAG} ${VESPER_ARGS[*]}"
381
+
382
+ if [[ "$SKIP_COMBINE" == false ]]; then
383
+ echo "=== Stage 3: Combine Stages ==="
384
+ run_in_container cpu \
385
+ "cryozeta-combine-stages --dump_root $OUTPUT_DIR --output $OUTPUT_DIR/combined.cif"
386
+ fi
387
+
388
+ echo "=== Done. Results in: $OUTPUT_DIR ==="