hallucination / mechanistic_interp /scripts /select_features.sh
ToiTenBao's picture
Upload hallucination folder
a2ffd07 verified
Raw
History Blame Contribute Delete
5.85 kB
#!/bin/bash
# =============================================================================
# select_features.sh — pick top-k SAE features per language-model layer.
#
# Two modes:
# MODE=probe (default) — top-k by largest positive probe weight.
# PROBES_PATH can be an all-layers state-dict file
# OR a directory containing one .pth file per layer.
# (e.g. mechanistic_interp/probes/probes_gen_bathroom_toilet.pt
# or mechanistic_interp/probes/toilet/mid/)
# One run, one probe → one OBJECT.
# MODE=f1 — top-k by best per-feature F1 against an HF column
# (0/1 label).
# Positives : HF rows where OBJECT column == 1
# (includes rows where OTHER_OBJECT=1 too).
# Negatives : HF rows where OTHER_OBJECT=1 & OBJECT=0
# (contrastive, if OTHER_OBJECT is set)
# + random CC3M ids from NEG_JSONL (if set).
# At least one of OTHER_OBJECT or NEG_JSONL is required.
# To process multiple objects in one shot, set
# OBJECTS="bathroom toilet ..." — one JSON per object.
#
# Examples:
# # probe mode (single object: whatever the state-dict was trained for)
# OBJECT=toilet \
# PROBES_PATH=mechanistic_interp/probes/probes_gen_bathroom_toilet.pt \
# bash mechanistic_interp/scripts/select_features.sh
#
# # probe mode using per-layer .pth files from Train_Probe_SAE.py output
# # Directory structure: probes/toilet/mid/probe_mid_0.pth, probe_mid_1.pth, ...
# OBJECT=toilet \
# PROBES_PATH=mechanistic_interp/probes/toilet/mid \
# bash mechanistic_interp/scripts/select_features.sh
#
# # f1: contrastive HF negatives only (no random CC3M)
# MODE=f1 OBJECT=toilet OTHER_OBJECT=bathroom \
# HF_DATASET=pbcong/bathroom-toilet \
# bash mechanistic_interp/scripts/select_features.sh
#
# # f1: contrastive HF negatives + random CC3M negatives
# MODE=f1 OBJECT=toilet OTHER_OBJECT=bathroom \
# HF_DATASET=pbcong/bathroom-toilet \
# NEG_JSONL=mechanistic_interp/neg_cc3m_5k.json \
# bash mechanistic_interp/scripts/select_features.sh
#
# # f1 on a different HF dataset with two new columns (json negatives only)
# MODE=f1 OBJECTS="kitchen fridge" \
# HF_DATASET=youruser/kitchen-fridge \
# NEG_JSONL=mechanistic_interp/neg_cc3m_5k.json \
# bash mechanistic_interp/scripts/select_features.sh
#
# Output:
# ${OUT_DIR}/selected_features_${MODE}_${OBJ}_top${TOP_K}.json (one per object)
# =============================================================================
set -euo pipefail
cd "$(dirname "$0")/../.."
export PYTHONPATH="$(cd .. && pwd):$(pwd):${PYTHONPATH:-}"
MODE="${MODE:-probe}"
TOP_K="${TOP_K:-20}"
HOOK_TYPE="${HOOK_TYPE:-post}"
DEVICE="${DEVICE:-cuda:4}"
# Object selection. OBJECTS (plural) wins when set; otherwise fall back to
# OBJECT (singular). OBJECT is also what the legacy single-object call uses.
OBJECT="${OBJECT:-bathroom}"
OBJECTS="${OBJECTS:-}"
# Common paths.
OUT_DIR="${OUT_DIR:-mechanistic_interp/probes}"
# probe-mode
PROBES_PATH="${PROBES_PATH:-/data/caotue/SAE_checkpoints/16_d_model/probes_bathroom_epoch10}"
# f1-mode
SAE_CKPT="${SAE_CKPT:-/data/caotue/SAE_checkpoints/16_d_model/last.ckpt}"
HF_DATASET="${HF_DATASET:-pbcong/bathroom-toilet}"
OTHER_OBJECT="${OTHER_OBJECT:-toilet}" # contrastive column (e.g. 'bathroom' when OBJECT=toilet)
NEG_JSONL="${NEG_JSONL:-mechanistic_interp/neg_cc3m_5k.json}" # optional random-CC3M negatives JSON
IMG_BATCH="${IMG_BATCH:-16}"
DTYPE="${DTYPE:-bfloat16}"
# Resolve object list.
if [ -n "${OBJECTS}" ]; then
LIST=( ${OBJECTS} )
else
LIST=( "${OBJECT}" )
fi
echo "MODE=${MODE} TOP_K=${TOP_K} objects: ${LIST[*]}"
OK_LIST=()
FAIL_LIST=()
for OBJ in "${LIST[@]}"; do
OUT="${OUT_DIR}/selected_features_${MODE}_${OBJ}_top${TOP_K}.json"
ARGS=(
-m mechanistic_interp.select_features
--mode "${MODE}"
--top_k "${TOP_K}"
--out "${OUT}"
--hook_type "${HOOK_TYPE}"
--label "${OBJ}"
)
if [ "${MODE}" = "probe" ]; then
ARGS+=( --probes_path "${PROBES_PATH}" )
else
# f1 mode: --probe_type drives positives (rows where the named HF
# column == 1), --label is just the JSON tag, OUT path is unique per
# object, and each object runs in its own python process so a failure
# on one (e.g. OOM) doesn't block the others.
ARGS+=(
--sae_ckpt "${SAE_CKPT}"
--hf_dataset "${HF_DATASET}"
--probe_type "${OBJ}"
--device "${DEVICE}"
--img_batch "${IMG_BATCH}"
--dtype "${DTYPE}"
)
if [ -n "${OTHER_OBJECT}" ]; then
ARGS+=( --other_object "${OTHER_OBJECT}" )
fi
if [ -n "${NEG_JSONL}" ]; then
ARGS+=( --neg_jsonl "${NEG_JSONL}" )
fi
fi
echo
echo "[$(date +%T)] OBJECT=${OBJ}${OUT}"
echo "Running: python ${ARGS[*]}"
if python "${ARGS[@]}"; then
echo "[$(date +%T)] OK ${OBJ}"
OK_LIST+=( "${OBJ}" )
else
rc=$?
echo "[$(date +%T)] FAIL ${OBJ} (exit ${rc})"
FAIL_LIST+=( "${OBJ}" )
fi
done
echo
echo "── Summary ────────────────────────────────────────────────"
echo "OK (${#OK_LIST[@]}): ${OK_LIST[*]:-—}"
echo "FAIL (${#FAIL_LIST[@]}): ${FAIL_LIST[*]:-—}"
if [ "${#FAIL_LIST[@]}" -gt 0 ]; then
exit 1
fi