Spaces:
Sleeping
Sleeping
Jac-Zac commited on
Commit ·
a1b8512
1
Parent(s): 7460198
General cleanups
Browse files- app.py +22 -61
- pyproject.toml +1 -1
- tabs/probe.py +25 -30
- tests/test_probes.py +3 -39
- utils/contrast.py +3 -7
- utils/preload.py +9 -0
- utils/probe_trace.py +10 -16
- utils/probes.py +13 -53
- utils/runtime.py +6 -19
- uv.lock +34 -34
app.py
CHANGED
|
@@ -5,7 +5,7 @@ import streamlit as st
|
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
|
| 7 |
from utils.analysis_sources import DEFAULT_COMPARE_MODEL, DEFAULT_HUB_REPO, SOURCE_HUB
|
| 8 |
-
from utils.helpers import DATASET_SOURCES, session_key
|
| 9 |
from utils.preload import preload_once
|
| 10 |
from utils.runtime import configured_ndif_api_key, list_remote_models
|
| 11 |
from utils.theme import active_base, install_catppuccin_theme
|
|
@@ -53,67 +53,28 @@ _TAB_PRELOAD_FUNCTIONS = {
|
|
| 53 |
def _hub_metadata_preload_calls() -> tuple[
|
| 54 |
tuple[str, tuple[str, str, str, str | None]], ...
|
| 55 |
]:
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
)
|
| 70 |
-
|
| 71 |
-
analysis_source = st.session_state.get("analysis:last_source", shared_source)
|
| 72 |
-
if analysis_source == SOURCE_HUB:
|
| 73 |
-
repo = st.session_state.get(
|
| 74 |
-
"analysis:hub_repo",
|
| 75 |
-
st.session_state.get("source:hub_repo", DEFAULT_HUB_REPO),
|
| 76 |
-
)
|
| 77 |
-
mask_strategy = st.session_state.get(
|
| 78 |
-
"analysis:last_mask_strategy",
|
| 79 |
-
shared_mask_strategy,
|
| 80 |
-
)
|
| 81 |
-
model = st.session_state.get(
|
| 82 |
-
widget_key("load", "hub_model", repo, mask_strategy),
|
| 83 |
-
st.session_state.get(
|
| 84 |
-
"analysis:hub_model_fallback",
|
| 85 |
-
st.session_state.get("source:hub_model", DEFAULT_COMPARE_MODEL),
|
| 86 |
-
),
|
| 87 |
-
)
|
| 88 |
-
variant = st.session_state.get(
|
| 89 |
-
"analysis:last_projection_variant",
|
| 90 |
-
st.session_state.get("analysis:last_similarity_variant"),
|
| 91 |
-
)
|
| 92 |
-
add(repo, model, mask_strategy, variant)
|
| 93 |
-
|
| 94 |
-
probe_source = st.session_state.get(widget_key("probe", "source"), shared_source)
|
| 95 |
-
if probe_source == SOURCE_HUB:
|
| 96 |
-
repo = st.session_state.get(
|
| 97 |
-
"probe:hub_repo",
|
| 98 |
-
st.session_state.get("source:hub_repo", DEFAULT_HUB_REPO),
|
| 99 |
-
)
|
| 100 |
-
mask_strategy = st.session_state.get(
|
| 101 |
-
"probe:last_mask_strategy",
|
| 102 |
-
shared_mask_strategy,
|
| 103 |
-
)
|
| 104 |
-
model = st.session_state.get(
|
| 105 |
-
widget_key("probe", "hub_model", repo, mask_strategy),
|
| 106 |
-
st.session_state.get(
|
| 107 |
-
"probe:hub_model_fallback",
|
| 108 |
-
st.session_state.get("source:hub_model", DEFAULT_COMPARE_MODEL),
|
| 109 |
-
),
|
| 110 |
-
)
|
| 111 |
-
add(repo, model, mask_strategy, st.session_state.get("probe:variant"))
|
| 112 |
-
|
| 113 |
-
deduped: dict[tuple[str, tuple[str, str, str, str | None]], None] = {}
|
| 114 |
-
for call in calls:
|
| 115 |
-
deduped[call] = None
|
| 116 |
-
return tuple(deduped)
|
| 117 |
|
| 118 |
|
| 119 |
@dataclass(frozen=True)
|
|
|
|
| 5 |
from dotenv import load_dotenv
|
| 6 |
|
| 7 |
from utils.analysis_sources import DEFAULT_COMPARE_MODEL, DEFAULT_HUB_REPO, SOURCE_HUB
|
| 8 |
+
from utils.helpers import DATASET_SOURCES, session_key
|
| 9 |
from utils.preload import preload_once
|
| 10 |
from utils.runtime import configured_ndif_api_key, list_remote_models
|
| 11 |
from utils.theme import active_base, install_catppuccin_theme
|
|
|
|
| 53 |
def _hub_metadata_preload_calls() -> tuple[
|
| 54 |
tuple[str, tuple[str, str, str, str | None]], ...
|
| 55 |
]:
|
| 56 |
+
"""Warm Hub metadata for the currently-selected source, if any.
|
| 57 |
+
|
| 58 |
+
Reads only the shared ``source:*`` keys (written by ``utils.source_controls``)
|
| 59 |
+
so this stays a small, single-purpose preload. Per-tab divergence used to
|
| 60 |
+
matter when each tab picked its own model; the shared keys are now the
|
| 61 |
+
source of truth.
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
if st.session_state.get("source:last_source", SOURCE_HUB) != SOURCE_HUB:
|
| 65 |
+
return ()
|
| 66 |
+
repo = st.session_state.get("source:hub_repo", DEFAULT_HUB_REPO)
|
| 67 |
+
model = st.session_state.get("source:hub_model", DEFAULT_COMPARE_MODEL)
|
| 68 |
+
mask_strategy = st.session_state.get("source:last_mask_strategy", "answer_mean")
|
| 69 |
+
variant = st.session_state.get("probe:variant") or st.session_state.get(
|
| 70 |
+
"analysis:last_projection_variant"
|
| 71 |
+
)
|
| 72 |
+
return (
|
| 73 |
+
(
|
| 74 |
+
"utils.analysis_sources:prefetch_hub_metadata",
|
| 75 |
+
(repo, model, mask_strategy, variant),
|
| 76 |
+
),
|
| 77 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
|
| 80 |
@dataclass(frozen=True)
|
pyproject.toml
CHANGED
|
@@ -5,7 +5,7 @@ description = "Streamlit UI for persona-vectors"
|
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.12"
|
| 7 |
dependencies = [
|
| 8 |
-
"persona-vectors>=0.8.
|
| 9 |
"datasets>=4.8.5",
|
| 10 |
"huggingface-hub>=1.14.0",
|
| 11 |
"streamlit>=1.44.0",
|
|
|
|
| 5 |
readme = "README.md"
|
| 6 |
requires-python = ">=3.12"
|
| 7 |
dependencies = [
|
| 8 |
+
"persona-vectors>=0.8.7",
|
| 9 |
"datasets>=4.8.5",
|
| 10 |
"huggingface-hub>=1.14.0",
|
| 11 |
"streamlit>=1.44.0",
|
tabs/probe.py
CHANGED
|
@@ -88,42 +88,32 @@ def _select_personas(
|
|
| 88 |
if not all_ids:
|
| 89 |
st.info("No personas found for this variant.")
|
| 90 |
return []
|
| 91 |
-
|
| 92 |
-
if len(regular) < 2:
|
| 93 |
st.info("At least two non-assistant personas are needed for probing.")
|
| 94 |
return []
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
st.warning(
|
| 99 |
f"Only {count} non-assistant personas are available; using all of them."
|
| 100 |
)
|
| 101 |
-
st.session_state["probe:persona_count"] = count
|
| 102 |
-
persona_ids = regular
|
| 103 |
-
persona_names_cached(
|
| 104 |
-
source,
|
| 105 |
-
location,
|
| 106 |
-
model_name,
|
| 107 |
-
mask_strategy.value,
|
| 108 |
-
(variant,),
|
| 109 |
-
tuple(persona_ids),
|
| 110 |
-
)
|
| 111 |
-
st.caption(f"Probing {len(persona_ids)} non-assistant personas.")
|
| 112 |
-
return persona_ids
|
| 113 |
|
| 114 |
-
default_count = min(
|
| 115 |
-
len(regular),
|
| 116 |
-
max(min_count, st.session_state.get("probe:persona_count", len(regular))),
|
| 117 |
-
)
|
| 118 |
-
count = st.slider(
|
| 119 |
-
"Personas",
|
| 120 |
-
min_value=min_count,
|
| 121 |
-
max_value=len(regular),
|
| 122 |
-
value=default_count,
|
| 123 |
-
key="probe:persona_count_slider",
|
| 124 |
-
)
|
| 125 |
st.session_state["probe:persona_count"] = count
|
| 126 |
-
persona_ids =
|
| 127 |
persona_names_cached(
|
| 128 |
source,
|
| 129 |
location,
|
|
@@ -132,7 +122,12 @@ def _select_personas(
|
|
| 132 |
(variant,),
|
| 133 |
tuple(persona_ids),
|
| 134 |
)
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
return persona_ids
|
| 137 |
|
| 138 |
|
|
|
|
| 88 |
if not all_ids:
|
| 89 |
st.info("No personas found for this variant.")
|
| 90 |
return []
|
| 91 |
+
if len(all_ids) < 2:
|
|
|
|
| 92 |
st.info("At least two non-assistant personas are needed for probing.")
|
| 93 |
return []
|
| 94 |
+
|
| 95 |
+
min_count = min(10, len(all_ids))
|
| 96 |
+
has_slider = min_count < len(all_ids)
|
| 97 |
+
if has_slider:
|
| 98 |
+
default_count = max(
|
| 99 |
+
min_count,
|
| 100 |
+
min(len(all_ids), st.session_state.get("probe:persona_count", len(all_ids))),
|
| 101 |
+
)
|
| 102 |
+
count = st.slider(
|
| 103 |
+
"Personas",
|
| 104 |
+
min_value=min_count,
|
| 105 |
+
max_value=len(all_ids),
|
| 106 |
+
value=default_count,
|
| 107 |
+
key="probe:persona_count_slider",
|
| 108 |
+
)
|
| 109 |
+
else:
|
| 110 |
+
count = len(all_ids)
|
| 111 |
st.warning(
|
| 112 |
f"Only {count} non-assistant personas are available; using all of them."
|
| 113 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
st.session_state["probe:persona_count"] = count
|
| 116 |
+
persona_ids = all_ids[:count]
|
| 117 |
persona_names_cached(
|
| 118 |
source,
|
| 119 |
location,
|
|
|
|
| 122 |
(variant,),
|
| 123 |
tuple(persona_ids),
|
| 124 |
)
|
| 125 |
+
if has_slider:
|
| 126 |
+
st.caption(
|
| 127 |
+
f"Probing {len(persona_ids)} of {len(all_ids)} non-assistant personas."
|
| 128 |
+
)
|
| 129 |
+
else:
|
| 130 |
+
st.caption(f"Probing {len(persona_ids)} non-assistant personas.")
|
| 131 |
return persona_ids
|
| 132 |
|
| 133 |
|
tests/test_probes.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
| 1 |
"""Regression tests for utils.probes.
|
| 2 |
|
| 3 |
Covers the probe-artifact filename parser (both naming conventions) and the
|
| 4 |
-
|
| 5 |
|
| 6 |
* ``_normalize_batch`` applies PCA independently of the scaler (previously the
|
| 7 |
PCA branch was unreachable when no scaler was present).
|
| 8 |
-
* ``LoadedProbe.run`` predicts class 1 for a single-output probe whose sigmoid
|
| 9 |
-
score is >= 0.5 (previously it always predicted class 0).
|
| 10 |
"""
|
| 11 |
|
| 12 |
import pytest
|
|
@@ -162,41 +160,6 @@ def test_normalize_batch_pca_shape_mismatch_raises():
|
|
| 162 |
probe._normalize_batch(torch.zeros(1, 3))
|
| 163 |
|
| 164 |
|
| 165 |
-
# --------------------------------------------------------------------------- #
|
| 166 |
-
# LoadedProbe.run — single-output prediction
|
| 167 |
-
# --------------------------------------------------------------------------- #
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
def _single_output_probe(weight: list[float], bias: float) -> LoadedProbe:
|
| 171 |
-
model = _LinearProbe(input_dim=len(weight), num_classes=1)
|
| 172 |
-
with torch.no_grad():
|
| 173 |
-
model.linear.weight.copy_(torch.tensor([weight]))
|
| 174 |
-
model.linear.bias.copy_(torch.tensor([bias]))
|
| 175 |
-
return LoadedProbe(
|
| 176 |
-
model=model,
|
| 177 |
-
input_dim=len(weight),
|
| 178 |
-
labels=["neg", "pos"],
|
| 179 |
-
model_type="linear",
|
| 180 |
-
layer=0,
|
| 181 |
-
location=None,
|
| 182 |
-
)
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
def test_run_single_output_predicts_positive_when_score_high():
|
| 186 |
-
"""Regression: single-output probe must predict class 1 when sigmoid >= 0.5."""
|
| 187 |
-
probe = _single_output_probe(weight=[1.0, 1.0], bias=5.0)
|
| 188 |
-
result = probe.run(torch.tensor([1.0, 1.0]))
|
| 189 |
-
assert result.predicted_index == 1
|
| 190 |
-
assert result.predicted_label == "pos"
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
def test_run_single_output_predicts_negative_when_score_low():
|
| 194 |
-
probe = _single_output_probe(weight=[1.0, 1.0], bias=-5.0)
|
| 195 |
-
result = probe.run(torch.tensor([1.0, 1.0]))
|
| 196 |
-
assert result.predicted_index == 0
|
| 197 |
-
assert result.predicted_label == "neg"
|
| 198 |
-
|
| 199 |
-
|
| 200 |
# --------------------------------------------------------------------------- #
|
| 201 |
# canonical persona-vectors artifacts
|
| 202 |
# --------------------------------------------------------------------------- #
|
|
@@ -224,4 +187,5 @@ def test_loaded_probe_from_canonical_artifact():
|
|
| 224 |
)
|
| 225 |
assert probe.labels == ["neg", "pos"]
|
| 226 |
assert probe.layer == 3
|
| 227 |
-
|
|
|
|
|
|
| 1 |
"""Regression tests for utils.probes.
|
| 2 |
|
| 3 |
Covers the probe-artifact filename parser (both naming conventions) and the
|
| 4 |
+
correctness fix:
|
| 5 |
|
| 6 |
* ``_normalize_batch`` applies PCA independently of the scaler (previously the
|
| 7 |
PCA branch was unreachable when no scaler was present).
|
|
|
|
|
|
|
| 8 |
"""
|
| 9 |
|
| 10 |
import pytest
|
|
|
|
| 160 |
probe._normalize_batch(torch.zeros(1, 3))
|
| 161 |
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
# --------------------------------------------------------------------------- #
|
| 164 |
# canonical persona-vectors artifacts
|
| 165 |
# --------------------------------------------------------------------------- #
|
|
|
|
| 187 |
)
|
| 188 |
assert probe.labels == ["neg", "pos"]
|
| 189 |
assert probe.layer == 3
|
| 190 |
+
_, _, predicted = probe.run_batch(torch.tensor([[1.0, 0.0]]))
|
| 191 |
+
assert probe.labels[int(predicted[0])] == "pos"
|
utils/contrast.py
CHANGED
|
@@ -49,12 +49,10 @@ def _strip_special_ids(
|
|
| 49 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 50 |
"""Return display ids and a mask that excludes special tokens."""
|
| 51 |
ids = ids.cpu()
|
| 52 |
-
special_ids =
|
| 53 |
if not special_ids or ids.numel() == 0:
|
| 54 |
return ids, torch.ones(ids.shape[0], dtype=torch.bool)
|
| 55 |
-
keep = torch.tensor(
|
| 56 |
-
[tid.item() not in special_ids for tid in ids], dtype=torch.bool
|
| 57 |
-
)
|
| 58 |
return ids[keep], keep
|
| 59 |
|
| 60 |
|
|
@@ -67,9 +65,7 @@ def _prepare_trace_input_ids(
|
|
| 67 |
context_prompt, _ = format_generation_prompt(context_messages, tokenizer)
|
| 68 |
context_ids = tokenizer(context_prompt, return_tensors="pt").input_ids[0]
|
| 69 |
input_ids = torch.cat([context_ids.cpu(), response_ids.detach().cpu()])
|
| 70 |
-
|
| 71 |
-
n_resp = len(response_ids)
|
| 72 |
-
return input_ids, n_ctx, n_resp
|
| 73 |
|
| 74 |
|
| 75 |
def _build_contrast(
|
|
|
|
| 49 |
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 50 |
"""Return display ids and a mask that excludes special tokens."""
|
| 51 |
ids = ids.cpu()
|
| 52 |
+
special_ids = sorted(getattr(tokenizer, "all_special_ids", []) or [])
|
| 53 |
if not special_ids or ids.numel() == 0:
|
| 54 |
return ids, torch.ones(ids.shape[0], dtype=torch.bool)
|
| 55 |
+
keep = ~torch.isin(ids, torch.tensor(special_ids, dtype=ids.dtype))
|
|
|
|
|
|
|
| 56 |
return ids[keep], keep
|
| 57 |
|
| 58 |
|
|
|
|
| 65 |
context_prompt, _ = format_generation_prompt(context_messages, tokenizer)
|
| 66 |
context_ids = tokenizer(context_prompt, return_tensors="pt").input_ids[0]
|
| 67 |
input_ids = torch.cat([context_ids.cpu(), response_ids.detach().cpu()])
|
| 68 |
+
return input_ids, len(context_ids), len(response_ids)
|
|
|
|
|
|
|
| 69 |
|
| 70 |
|
| 71 |
def _build_contrast(
|
utils/preload.py
CHANGED
|
@@ -82,4 +82,13 @@ def preload_once(
|
|
| 82 |
name=f"persona-ui-preload-{name}",
|
| 83 |
daemon=True,
|
| 84 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
thread.start()
|
|
|
|
| 82 |
name=f"persona-ui-preload-{name}",
|
| 83 |
daemon=True,
|
| 84 |
)
|
| 85 |
+
# Attach this Streamlit script's run context so calls into ``@st.cache_*``
|
| 86 |
+
# functions land in the right session instead of emitting a
|
| 87 |
+
# "missing ScriptRunContext" warning per call.
|
| 88 |
+
try:
|
| 89 |
+
from streamlit.runtime.scriptrunner import add_script_run_ctx
|
| 90 |
+
|
| 91 |
+
add_script_run_ctx(thread)
|
| 92 |
+
except Exception:
|
| 93 |
+
logger.debug("Could not attach script run context to preload thread")
|
| 94 |
thread.start()
|
utils/probe_trace.py
CHANGED
|
@@ -58,7 +58,6 @@ def trace_conversation(
|
|
| 58 |
model.tokenizer,
|
| 59 |
add_generation_prompt=False,
|
| 60 |
)
|
| 61 |
-
assistant_mask_seq = _compute_assistant_mask(model.tokenizer, messages)
|
| 62 |
prompt_hash = hashlib.sha256(prompt_text.encode("utf-8")).hexdigest()
|
| 63 |
cache_key = _trace_cache_key(
|
| 64 |
model_name=model_name,
|
|
@@ -71,6 +70,8 @@ def trace_conversation(
|
|
| 71 |
if cached is not None:
|
| 72 |
return cached
|
| 73 |
|
|
|
|
|
|
|
| 74 |
accessor = _select_accessor(model, location)
|
| 75 |
if remote:
|
| 76 |
from utils.runtime import remote_backend
|
|
@@ -199,19 +200,15 @@ def _drop_derived_results_for_trace(cache_key: str) -> None:
|
|
| 199 |
f"probe_values::{cache_key}::",
|
| 200 |
)
|
| 201 |
tracked = st.session_state.get(_DERIVED_CACHE_TRACKER_KEY)
|
| 202 |
-
if isinstance(tracked, list):
|
| 203 |
-
kept: list[str] = []
|
| 204 |
-
for key in tracked:
|
| 205 |
-
if isinstance(key, str) and key.startswith(prefixes):
|
| 206 |
-
st.session_state.pop(key, None)
|
| 207 |
-
else:
|
| 208 |
-
kept.append(key)
|
| 209 |
-
st.session_state[_DERIVED_CACHE_TRACKER_KEY] = kept
|
| 210 |
return
|
| 211 |
-
|
| 212 |
-
for key in
|
| 213 |
if isinstance(key, str) and key.startswith(prefixes):
|
| 214 |
st.session_state.pop(key, None)
|
|
|
|
|
|
|
|
|
|
| 215 |
|
| 216 |
|
| 217 |
def _compute_assistant_mask(
|
|
@@ -395,10 +392,7 @@ def _assistant_spans(
|
|
| 395 |
|
| 396 |
|
| 397 |
def _special_token_mask(tokenizer: object, input_ids: torch.Tensor) -> torch.Tensor:
|
| 398 |
-
special_ids =
|
| 399 |
if not special_ids:
|
| 400 |
return torch.zeros(int(input_ids.shape[0]), dtype=torch.bool)
|
| 401 |
-
return torch.tensor(
|
| 402 |
-
[int(token_id) in special_ids for token_id in input_ids.tolist()],
|
| 403 |
-
dtype=torch.bool,
|
| 404 |
-
)
|
|
|
|
| 58 |
model.tokenizer,
|
| 59 |
add_generation_prompt=False,
|
| 60 |
)
|
|
|
|
| 61 |
prompt_hash = hashlib.sha256(prompt_text.encode("utf-8")).hexdigest()
|
| 62 |
cache_key = _trace_cache_key(
|
| 63 |
model_name=model_name,
|
|
|
|
| 70 |
if cached is not None:
|
| 71 |
return cached
|
| 72 |
|
| 73 |
+
assistant_mask_seq = _compute_assistant_mask(model.tokenizer, messages)
|
| 74 |
+
|
| 75 |
accessor = _select_accessor(model, location)
|
| 76 |
if remote:
|
| 77 |
from utils.runtime import remote_backend
|
|
|
|
| 200 |
f"probe_values::{cache_key}::",
|
| 201 |
)
|
| 202 |
tracked = st.session_state.get(_DERIVED_CACHE_TRACKER_KEY)
|
| 203 |
+
if not isinstance(tracked, list):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
return
|
| 205 |
+
kept: list[str] = []
|
| 206 |
+
for key in tracked:
|
| 207 |
if isinstance(key, str) and key.startswith(prefixes):
|
| 208 |
st.session_state.pop(key, None)
|
| 209 |
+
else:
|
| 210 |
+
kept.append(key)
|
| 211 |
+
st.session_state[_DERIVED_CACHE_TRACKER_KEY] = kept
|
| 212 |
|
| 213 |
|
| 214 |
def _compute_assistant_mask(
|
|
|
|
| 392 |
|
| 393 |
|
| 394 |
def _special_token_mask(tokenizer: object, input_ids: torch.Tensor) -> torch.Tensor:
|
| 395 |
+
special_ids = sorted(getattr(tokenizer, "all_special_ids", []) or [])
|
| 396 |
if not special_ids:
|
| 397 |
return torch.zeros(int(input_ids.shape[0]), dtype=torch.bool)
|
| 398 |
+
return torch.isin(input_ids.cpu(), torch.tensor(special_ids, dtype=input_ids.dtype))
|
|
|
|
|
|
|
|
|
utils/probes.py
CHANGED
|
@@ -21,15 +21,6 @@ from utils.probe_files import (
|
|
| 21 |
_PROBE_CACHE_ENTRIES = env_int("PERSONA_UI_PROBE_CACHE_ENTRIES", 8)
|
| 22 |
|
| 23 |
|
| 24 |
-
@dataclass(frozen=True)
|
| 25 |
-
class ProbeRunResult:
|
| 26 |
-
input_dim: int
|
| 27 |
-
logits: torch.Tensor
|
| 28 |
-
probabilities: torch.Tensor
|
| 29 |
-
predicted_index: int
|
| 30 |
-
predicted_label: str | None
|
| 31 |
-
|
| 32 |
-
|
| 33 |
class _LinearProbe(nn.Module):
|
| 34 |
def __init__(self, input_dim: int, num_classes: int):
|
| 35 |
super().__init__()
|
|
@@ -85,6 +76,15 @@ class LoadedProbe:
|
|
| 85 |
|
| 86 |
def __post_init__(self) -> None:
|
| 87 |
self.model.eval()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
@property
|
| 90 |
def is_regression(self) -> bool:
|
|
@@ -113,43 +113,6 @@ class LoadedProbe:
|
|
| 113 |
outputs = outputs.unsqueeze(-1)
|
| 114 |
return outputs
|
| 115 |
|
| 116 |
-
def run(self, vector: torch.Tensor) -> ProbeRunResult:
|
| 117 |
-
if vector.ndim != 1:
|
| 118 |
-
raise ValueError(
|
| 119 |
-
f"Probe expects a 1D activation vector, got shape {tuple(vector.shape)}"
|
| 120 |
-
)
|
| 121 |
-
if vector.shape[0] != self.input_dim:
|
| 122 |
-
raise ValueError(
|
| 123 |
-
f"Probe expects input dim {self.input_dim}, got {vector.shape[0]}"
|
| 124 |
-
)
|
| 125 |
-
|
| 126 |
-
batch = vector.detach().to(dtype=torch.float32, device="cpu").unsqueeze(0)
|
| 127 |
-
logits_batch, probs_batch = self._forward_batch(batch)
|
| 128 |
-
logits = logits_batch.squeeze(0)
|
| 129 |
-
probs = probs_batch.squeeze(0)
|
| 130 |
-
if logits.ndim == 0:
|
| 131 |
-
logits = logits.unsqueeze(0)
|
| 132 |
-
if probs.ndim == 0:
|
| 133 |
-
probs = probs.unsqueeze(0)
|
| 134 |
-
|
| 135 |
-
predicted_index = (
|
| 136 |
-
int(probs.item() >= 0.5)
|
| 137 |
-
if probs.numel() == 1
|
| 138 |
-
else int(torch.argmax(probs).item())
|
| 139 |
-
)
|
| 140 |
-
predicted_label = (
|
| 141 |
-
self.labels[predicted_index]
|
| 142 |
-
if 0 <= predicted_index < len(self.labels)
|
| 143 |
-
else None
|
| 144 |
-
)
|
| 145 |
-
return ProbeRunResult(
|
| 146 |
-
input_dim=int(vector.shape[0]),
|
| 147 |
-
logits=logits,
|
| 148 |
-
probabilities=probs,
|
| 149 |
-
predicted_index=predicted_index,
|
| 150 |
-
predicted_label=predicted_label,
|
| 151 |
-
)
|
| 152 |
-
|
| 153 |
def run_batch(
|
| 154 |
self, activations: torch.Tensor
|
| 155 |
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
@@ -188,8 +151,7 @@ class LoadedProbe:
|
|
| 188 |
|
| 189 |
def _normalize_batch(self, batch: torch.Tensor) -> torch.Tensor:
|
| 190 |
if self.scaler_mean is not None and self.scaler_std is not None:
|
| 191 |
-
mean = self.scaler_mean.
|
| 192 |
-
std = self.scaler_std.to(dtype=torch.float32)
|
| 193 |
if mean.ndim != 1 or std.ndim != 1 or mean.shape[0] != batch.shape[1]:
|
| 194 |
raise ValueError(
|
| 195 |
"Probe scaler shape does not match activation hidden size: "
|
|
@@ -199,14 +161,12 @@ class LoadedProbe:
|
|
| 199 |
safe_std = torch.where(std == 0, torch.ones_like(std), std)
|
| 200 |
batch = (batch - mean) / safe_std
|
| 201 |
if self.pca_mean is not None and self.pca_components is not None:
|
| 202 |
-
pca_mean = self.pca_mean.
|
| 203 |
-
components = self.pca_components.to(dtype=torch.float32)
|
| 204 |
-
if pca_mean.ndim != 1 or pca_mean.shape[0] != batch.shape[1]:
|
| 205 |
raise ValueError(
|
| 206 |
"Probe PCA mean shape does not match activation hidden size: "
|
| 207 |
-
f"mean={tuple(pca_mean.shape)} batch={tuple(batch.shape)}"
|
| 208 |
)
|
| 209 |
-
batch = (batch - pca_mean) @
|
| 210 |
return batch
|
| 211 |
|
| 212 |
|
|
|
|
| 21 |
_PROBE_CACHE_ENTRIES = env_int("PERSONA_UI_PROBE_CACHE_ENTRIES", 8)
|
| 22 |
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
class _LinearProbe(nn.Module):
|
| 25 |
def __init__(self, input_dim: int, num_classes: int):
|
| 26 |
super().__init__()
|
|
|
|
| 76 |
|
| 77 |
def __post_init__(self) -> None:
|
| 78 |
self.model.eval()
|
| 79 |
+
# Cast normalization tensors once so per-forward calls don't redo it.
|
| 80 |
+
if self.scaler_mean is not None:
|
| 81 |
+
self.scaler_mean = self.scaler_mean.to(dtype=torch.float32)
|
| 82 |
+
if self.scaler_std is not None:
|
| 83 |
+
self.scaler_std = self.scaler_std.to(dtype=torch.float32)
|
| 84 |
+
if self.pca_mean is not None:
|
| 85 |
+
self.pca_mean = self.pca_mean.to(dtype=torch.float32)
|
| 86 |
+
if self.pca_components is not None:
|
| 87 |
+
self.pca_components = self.pca_components.to(dtype=torch.float32)
|
| 88 |
|
| 89 |
@property
|
| 90 |
def is_regression(self) -> bool:
|
|
|
|
| 113 |
outputs = outputs.unsqueeze(-1)
|
| 114 |
return outputs
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
def run_batch(
|
| 117 |
self, activations: torch.Tensor
|
| 118 |
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
|
|
| 151 |
|
| 152 |
def _normalize_batch(self, batch: torch.Tensor) -> torch.Tensor:
|
| 153 |
if self.scaler_mean is not None and self.scaler_std is not None:
|
| 154 |
+
mean, std = self.scaler_mean, self.scaler_std
|
|
|
|
| 155 |
if mean.ndim != 1 or std.ndim != 1 or mean.shape[0] != batch.shape[1]:
|
| 156 |
raise ValueError(
|
| 157 |
"Probe scaler shape does not match activation hidden size: "
|
|
|
|
| 161 |
safe_std = torch.where(std == 0, torch.ones_like(std), std)
|
| 162 |
batch = (batch - mean) / safe_std
|
| 163 |
if self.pca_mean is not None and self.pca_components is not None:
|
| 164 |
+
if self.pca_mean.ndim != 1 or self.pca_mean.shape[0] != batch.shape[1]:
|
|
|
|
|
|
|
| 165 |
raise ValueError(
|
| 166 |
"Probe PCA mean shape does not match activation hidden size: "
|
| 167 |
+
f"mean={tuple(self.pca_mean.shape)} batch={tuple(batch.shape)}"
|
| 168 |
)
|
| 169 |
+
batch = (batch - self.pca_mean) @ self.pca_components.T
|
| 170 |
return batch
|
| 171 |
|
| 172 |
|
utils/runtime.py
CHANGED
|
@@ -113,7 +113,8 @@ def configured_ndif_api_key() -> str | None:
|
|
| 113 |
def remote_backend(model: object, api_key: str | None = None, *, on_status=None):
|
| 114 |
"""Build an NDIF backend with credentials bound to one browser session."""
|
| 115 |
|
| 116 |
-
from nnsight.intervention.backends.remote import
|
|
|
|
| 117 |
|
| 118 |
active_key = api_key or session_ndif_api_key() or configured_ndif_api_key()
|
| 119 |
if not active_key:
|
|
@@ -121,24 +122,10 @@ def remote_backend(model: object, api_key: str | None = None, *, on_status=None)
|
|
| 121 |
|
| 122 |
backend = RemoteBackend(model.to_model_key(), api_key=active_key)
|
| 123 |
backend.CONNECT_TIMEOUT = 300.0
|
| 124 |
-
if on_status is None:
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
def update(
|
| 129 |
-
self,
|
| 130 |
-
job_id: str = "",
|
| 131 |
-
status_name: str = "",
|
| 132 |
-
description: str = "",
|
| 133 |
-
):
|
| 134 |
-
super().update(job_id, status_name, description)
|
| 135 |
-
if status_name:
|
| 136 |
-
on_status(job_id, status_name, description)
|
| 137 |
-
|
| 138 |
-
backend.status_display = _CallbackJobStatusDisplay(
|
| 139 |
-
enabled=True,
|
| 140 |
-
verbose=backend.verbose,
|
| 141 |
-
)
|
| 142 |
return backend
|
| 143 |
|
| 144 |
|
|
|
|
| 113 |
def remote_backend(model: object, api_key: str | None = None, *, on_status=None):
|
| 114 |
"""Build an NDIF backend with credentials bound to one browser session."""
|
| 115 |
|
| 116 |
+
from nnsight.intervention.backends.remote import RemoteBackend
|
| 117 |
+
from persona_vectors.activations import CallbackJobStatusDisplay
|
| 118 |
|
| 119 |
active_key = api_key or session_ndif_api_key() or configured_ndif_api_key()
|
| 120 |
if not active_key:
|
|
|
|
| 122 |
|
| 123 |
backend = RemoteBackend(model.to_model_key(), api_key=active_key)
|
| 124 |
backend.CONNECT_TIMEOUT = 300.0
|
| 125 |
+
if on_status is not None:
|
| 126 |
+
backend.status_display = CallbackJobStatusDisplay(
|
| 127 |
+
on_status, enabled=True, verbose=backend.verbose
|
| 128 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
return backend
|
| 130 |
|
| 131 |
|
uv.lock
CHANGED
|
@@ -30,11 +30,11 @@ wheels = [
|
|
| 30 |
|
| 31 |
[[package]]
|
| 32 |
name = "aiohappyeyeballs"
|
| 33 |
-
version = "2.6.
|
| 34 |
source = { registry = "https://pypi.org/simple" }
|
| 35 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 36 |
wheels = [
|
| 37 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 38 |
]
|
| 39 |
|
| 40 |
[[package]]
|
|
@@ -229,11 +229,11 @@ wheels = [
|
|
| 229 |
|
| 230 |
[[package]]
|
| 231 |
name = "cachetools"
|
| 232 |
-
version = "7.1.
|
| 233 |
source = { registry = "https://pypi.org/simple" }
|
| 234 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 235 |
wheels = [
|
| 236 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 237 |
]
|
| 238 |
|
| 239 |
[[package]]
|
|
@@ -247,11 +247,11 @@ wheels = [
|
|
| 247 |
|
| 248 |
[[package]]
|
| 249 |
name = "certifi"
|
| 250 |
-
version = "2026.
|
| 251 |
source = { registry = "https://pypi.org/simple" }
|
| 252 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 253 |
wheels = [
|
| 254 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 255 |
]
|
| 256 |
|
| 257 |
[[package]]
|
|
@@ -343,14 +343,14 @@ wheels = [
|
|
| 343 |
|
| 344 |
[[package]]
|
| 345 |
name = "click"
|
| 346 |
-
version = "8.4.
|
| 347 |
source = { registry = "https://pypi.org/simple" }
|
| 348 |
dependencies = [
|
| 349 |
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 350 |
]
|
| 351 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 352 |
wheels = [
|
| 353 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 354 |
]
|
| 355 |
|
| 356 |
[[package]]
|
|
@@ -725,7 +725,7 @@ wheels = [
|
|
| 725 |
|
| 726 |
[[package]]
|
| 727 |
name = "huggingface-hub"
|
| 728 |
-
version = "1.
|
| 729 |
source = { registry = "https://pypi.org/simple" }
|
| 730 |
dependencies = [
|
| 731 |
{ name = "filelock" },
|
|
@@ -738,18 +738,18 @@ dependencies = [
|
|
| 738 |
{ name = "typer" },
|
| 739 |
{ name = "typing-extensions" },
|
| 740 |
]
|
| 741 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 742 |
wheels = [
|
| 743 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 744 |
]
|
| 745 |
|
| 746 |
[[package]]
|
| 747 |
name = "idna"
|
| 748 |
-
version = "3.
|
| 749 |
source = { registry = "https://pypi.org/simple" }
|
| 750 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 751 |
wheels = [
|
| 752 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 753 |
]
|
| 754 |
|
| 755 |
[[package]]
|
|
@@ -1608,7 +1608,7 @@ requires-dist = [
|
|
| 1608 |
{ name = "catppuccin", specifier = ">=2.5.0" },
|
| 1609 |
{ name = "datasets", specifier = ">=4.8.5" },
|
| 1610 |
{ name = "huggingface-hub", specifier = ">=1.14.0" },
|
| 1611 |
-
{ name = "persona-vectors", specifier = ">=0.8.
|
| 1612 |
{ name = "plotly", specifier = ">=6.6.0" },
|
| 1613 |
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
| 1614 |
{ name = "safetensors", specifier = ">=0.7.0" },
|
|
@@ -1620,7 +1620,7 @@ dev = [{ name = "pytest", specifier = ">=9.0.3" }]
|
|
| 1620 |
|
| 1621 |
[[package]]
|
| 1622 |
name = "persona-vectors"
|
| 1623 |
-
version = "0.8.
|
| 1624 |
source = { registry = "https://pypi.org/simple" }
|
| 1625 |
dependencies = [
|
| 1626 |
{ name = "datasets" },
|
|
@@ -1639,9 +1639,9 @@ dependencies = [
|
|
| 1639 |
{ name = "transformers" },
|
| 1640 |
{ name = "umap-learn" },
|
| 1641 |
]
|
| 1642 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1643 |
wheels = [
|
| 1644 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1645 |
]
|
| 1646 |
|
| 1647 |
[[package]]
|
|
@@ -2133,14 +2133,14 @@ wheels = [
|
|
| 2133 |
|
| 2134 |
[[package]]
|
| 2135 |
name = "python-engineio"
|
| 2136 |
-
version = "4.13.
|
| 2137 |
source = { registry = "https://pypi.org/simple" }
|
| 2138 |
dependencies = [
|
| 2139 |
{ name = "simple-websocket" },
|
| 2140 |
]
|
| 2141 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 2142 |
wheels = [
|
| 2143 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2144 |
]
|
| 2145 |
|
| 2146 |
[[package]]
|
|
@@ -2154,15 +2154,15 @@ wheels = [
|
|
| 2154 |
|
| 2155 |
[[package]]
|
| 2156 |
name = "python-socketio"
|
| 2157 |
-
version = "5.16.
|
| 2158 |
source = { registry = "https://pypi.org/simple" }
|
| 2159 |
dependencies = [
|
| 2160 |
{ name = "bidict" },
|
| 2161 |
{ name = "python-engineio" },
|
| 2162 |
]
|
| 2163 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 2164 |
wheels = [
|
| 2165 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2166 |
]
|
| 2167 |
|
| 2168 |
[package.optional-dependencies]
|
|
@@ -2672,15 +2672,15 @@ wheels = [
|
|
| 2672 |
|
| 2673 |
[[package]]
|
| 2674 |
name = "starlette"
|
| 2675 |
-
version = "1.0.
|
| 2676 |
source = { registry = "https://pypi.org/simple" }
|
| 2677 |
dependencies = [
|
| 2678 |
{ name = "anyio" },
|
| 2679 |
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 2680 |
]
|
| 2681 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 2682 |
wheels = [
|
| 2683 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2684 |
]
|
| 2685 |
|
| 2686 |
[[package]]
|
|
@@ -2882,7 +2882,7 @@ wheels = [
|
|
| 2882 |
|
| 2883 |
[[package]]
|
| 2884 |
name = "transformers"
|
| 2885 |
-
version = "5.
|
| 2886 |
source = { registry = "https://pypi.org/simple" }
|
| 2887 |
dependencies = [
|
| 2888 |
{ name = "huggingface-hub" },
|
|
@@ -2895,9 +2895,9 @@ dependencies = [
|
|
| 2895 |
{ name = "tqdm" },
|
| 2896 |
{ name = "typer" },
|
| 2897 |
]
|
| 2898 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 2899 |
wheels = [
|
| 2900 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2901 |
]
|
| 2902 |
|
| 2903 |
[[package]]
|
|
|
|
| 30 |
|
| 31 |
[[package]]
|
| 32 |
name = "aiohappyeyeballs"
|
| 33 |
+
version = "2.6.2"
|
| 34 |
source = { registry = "https://pypi.org/simple" }
|
| 35 |
+
sdist = { url = "https://files.pythonhosted.org/packages/33/c6/61a2d7b7572279226bb2e7f61d7a19ca7c90da0329c93fa0d560cbf288d8/aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64", size = 22591, upload-time = "2026-05-20T15:12:24.631Z" }
|
| 36 |
wheels = [
|
| 37 |
+
{ url = "https://files.pythonhosted.org/packages/5f/fc/a7bf5b6e4e617b45f90f2d9d2a68519c249c81dd4fc2658c7a2a61c4f4b7/aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4", size = 15062, upload-time = "2026-05-20T15:12:23.328Z" },
|
| 38 |
]
|
| 39 |
|
| 40 |
[[package]]
|
|
|
|
| 229 |
|
| 230 |
[[package]]
|
| 231 |
name = "cachetools"
|
| 232 |
+
version = "7.1.4"
|
| 233 |
source = { registry = "https://pypi.org/simple" }
|
| 234 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" }
|
| 235 |
wheels = [
|
| 236 |
+
{ url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" },
|
| 237 |
]
|
| 238 |
|
| 239 |
[[package]]
|
|
|
|
| 247 |
|
| 248 |
[[package]]
|
| 249 |
name = "certifi"
|
| 250 |
+
version = "2026.5.20"
|
| 251 |
source = { registry = "https://pypi.org/simple" }
|
| 252 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" }
|
| 253 |
wheels = [
|
| 254 |
+
{ url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
|
| 255 |
]
|
| 256 |
|
| 257 |
[[package]]
|
|
|
|
| 343 |
|
| 344 |
[[package]]
|
| 345 |
name = "click"
|
| 346 |
+
version = "8.4.1"
|
| 347 |
source = { registry = "https://pypi.org/simple" }
|
| 348 |
dependencies = [
|
| 349 |
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 350 |
]
|
| 351 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" }
|
| 352 |
wheels = [
|
| 353 |
+
{ url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" },
|
| 354 |
]
|
| 355 |
|
| 356 |
[[package]]
|
|
|
|
| 725 |
|
| 726 |
[[package]]
|
| 727 |
name = "huggingface-hub"
|
| 728 |
+
version = "1.16.1"
|
| 729 |
source = { registry = "https://pypi.org/simple" }
|
| 730 |
dependencies = [
|
| 731 |
{ name = "filelock" },
|
|
|
|
| 738 |
{ name = "typer" },
|
| 739 |
{ name = "typing-extensions" },
|
| 740 |
]
|
| 741 |
+
sdist = { url = "https://files.pythonhosted.org/packages/48/0f/ed994dbade67a54407c28cab96ef845e0e6d25500be56aca6394f8bfc9dd/huggingface_hub-1.16.1.tar.gz", hash = "sha256:7f1dc4c5ec21aed69be630ad0c3378616be16f3de1a47b141c0e812965d9c832", size = 792534, upload-time = "2026-05-21T18:40:00.908Z" }
|
| 742 |
wheels = [
|
| 743 |
+
{ url = "https://files.pythonhosted.org/packages/49/79/621a7dbb80c70974f73a597275351ebe03ce5bc65cb5f8f4acb5859252bc/huggingface_hub-1.16.1-py3-none-any.whl", hash = "sha256:64340de934b9ce37857ef85a82de72f5629e8a270f9119eabb12bf495eb53c22", size = 668176, upload-time = "2026-05-21T18:39:58.596Z" },
|
| 744 |
]
|
| 745 |
|
| 746 |
[[package]]
|
| 747 |
name = "idna"
|
| 748 |
+
version = "3.16"
|
| 749 |
source = { registry = "https://pypi.org/simple" }
|
| 750 |
+
sdist = { url = "https://files.pythonhosted.org/packages/1a/88/bcf9709822fe69d02c2a6a77956c98ce6ea8ca8767a9aadcedc7eb6a2390/idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d", size = 203770, upload-time = "2026-05-22T00:16:18.781Z" }
|
| 751 |
wheels = [
|
| 752 |
+
{ url = "https://files.pythonhosted.org/packages/94/16/70255075a9859a0e3adb789b68ceb0e210dec03934245fd98d248226572f/idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5", size = 74165, upload-time = "2026-05-22T00:16:16.698Z" },
|
| 753 |
]
|
| 754 |
|
| 755 |
[[package]]
|
|
|
|
| 1608 |
{ name = "catppuccin", specifier = ">=2.5.0" },
|
| 1609 |
{ name = "datasets", specifier = ">=4.8.5" },
|
| 1610 |
{ name = "huggingface-hub", specifier = ">=1.14.0" },
|
| 1611 |
+
{ name = "persona-vectors", specifier = ">=0.8.7" },
|
| 1612 |
{ name = "plotly", specifier = ">=6.6.0" },
|
| 1613 |
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
| 1614 |
{ name = "safetensors", specifier = ">=0.7.0" },
|
|
|
|
| 1620 |
|
| 1621 |
[[package]]
|
| 1622 |
name = "persona-vectors"
|
| 1623 |
+
version = "0.8.7"
|
| 1624 |
source = { registry = "https://pypi.org/simple" }
|
| 1625 |
dependencies = [
|
| 1626 |
{ name = "datasets" },
|
|
|
|
| 1639 |
{ name = "transformers" },
|
| 1640 |
{ name = "umap-learn" },
|
| 1641 |
]
|
| 1642 |
+
sdist = { url = "https://files.pythonhosted.org/packages/25/92/2d8a021cabfb23cbc7fd79cc1140607e424283bae324177ee3c54c673a7d/persona_vectors-0.8.7.tar.gz", hash = "sha256:ffc92effdaecb38d06a107a6690c47ce0a891ae86fb3ffbfba390c9e2ca59714", size = 43667, upload-time = "2026-05-23T09:48:37.504Z" }
|
| 1643 |
wheels = [
|
| 1644 |
+
{ url = "https://files.pythonhosted.org/packages/29/51/976d72e4a98fce3cdd058862adb879d7400aff9459d2f4af9a9cffde8c33/persona_vectors-0.8.7-py3-none-any.whl", hash = "sha256:ba5a0fb3f81b2750b82960f07700c223f07c31bc215ae3ab67fbd1f9d200419f", size = 53636, upload-time = "2026-05-23T09:48:36.554Z" },
|
| 1645 |
]
|
| 1646 |
|
| 1647 |
[[package]]
|
|
|
|
| 2133 |
|
| 2134 |
[[package]]
|
| 2135 |
name = "python-engineio"
|
| 2136 |
+
version = "4.13.2"
|
| 2137 |
source = { registry = "https://pypi.org/simple" }
|
| 2138 |
dependencies = [
|
| 2139 |
{ name = "simple-websocket" },
|
| 2140 |
]
|
| 2141 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fa/6d/4384c2723adad93a3d6de4297e6d9c8b93be7f778a407f34f6ee0b2bea3e/python_engineio-4.13.2.tar.gz", hash = "sha256:a7732e99cfb7db6ed1aee31f18d7f73bbae086a92f31dee019bc646155d9684e", size = 79639, upload-time = "2026-05-21T21:45:07.578Z" }
|
| 2142 |
wheels = [
|
| 2143 |
+
{ url = "https://files.pythonhosted.org/packages/b7/28/180bfc5c95e83d40cb2abce512684ccad44e4819ec899fc36cb404a19061/python_engineio-4.13.2-py3-none-any.whl", hash = "sha256:8c101cd170e400dc4e970cd523325cde22df8fc25140953f379327055d701a6b", size = 59993, upload-time = "2026-05-21T21:45:06.162Z" },
|
| 2144 |
]
|
| 2145 |
|
| 2146 |
[[package]]
|
|
|
|
| 2154 |
|
| 2155 |
[[package]]
|
| 2156 |
name = "python-socketio"
|
| 2157 |
+
version = "5.16.2"
|
| 2158 |
source = { registry = "https://pypi.org/simple" }
|
| 2159 |
dependencies = [
|
| 2160 |
{ name = "bidict" },
|
| 2161 |
{ name = "python-engineio" },
|
| 2162 |
]
|
| 2163 |
+
sdist = { url = "https://files.pythonhosted.org/packages/07/dd/6fd4112b941f7d39b8171b6ba17902609bd8fa2059c3812a3c29dade13e7/python_socketio-5.16.2.tar.gz", hash = "sha256:ad88c228d921646efa436c0a0df217e364ef30ec072df4041484e54d49c15989", size = 128011, upload-time = "2026-05-21T22:03:44.418Z" }
|
| 2164 |
wheels = [
|
| 2165 |
+
{ url = "https://files.pythonhosted.org/packages/72/dc/0decaf5da92a7a969374474025787102d811d42aed1d32191fa338620e15/python_socketio-5.16.2-py3-none-any.whl", hash = "sha256:bef2da3374fd533aed4297f57b4f6512b52aa51604cb0da2165f401291c5ca20", size = 82137, upload-time = "2026-05-21T22:03:42.616Z" },
|
| 2166 |
]
|
| 2167 |
|
| 2168 |
[package.optional-dependencies]
|
|
|
|
| 2672 |
|
| 2673 |
[[package]]
|
| 2674 |
name = "starlette"
|
| 2675 |
+
version = "1.0.1"
|
| 2676 |
source = { registry = "https://pypi.org/simple" }
|
| 2677 |
dependencies = [
|
| 2678 |
{ name = "anyio" },
|
| 2679 |
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 2680 |
]
|
| 2681 |
+
sdist = { url = "https://files.pythonhosted.org/packages/08/a3/84e821cc54b4ab50ae6dbc6ac3800a651b65ec35f045cc73785380654057/starlette-1.0.1.tar.gz", hash = "sha256:512399c5f1de7fac99c88572212ded9ddeddef2fb32afa82d724000e88b38f4f", size = 2659596, upload-time = "2026-05-21T21:58:58.433Z" }
|
| 2682 |
wheels = [
|
| 2683 |
+
{ url = "https://files.pythonhosted.org/packages/ec/e1/b2df4bc09a1e51ff664c1e17018a4274b42e5e9352e4a478ea540512dc88/starlette-1.0.1-py3-none-any.whl", hash = "sha256:7c0e69b2ee1c848bd54669d908500117a3ee13de603a21427e5c6fc1adf98dcd", size = 72802, upload-time = "2026-05-21T21:58:56.551Z" },
|
| 2684 |
]
|
| 2685 |
|
| 2686 |
[[package]]
|
|
|
|
| 2882 |
|
| 2883 |
[[package]]
|
| 2884 |
name = "transformers"
|
| 2885 |
+
version = "5.9.0"
|
| 2886 |
source = { registry = "https://pypi.org/simple" }
|
| 2887 |
dependencies = [
|
| 2888 |
{ name = "huggingface-hub" },
|
|
|
|
| 2895 |
{ name = "tqdm" },
|
| 2896 |
{ name = "typer" },
|
| 2897 |
]
|
| 2898 |
+
sdist = { url = "https://files.pythonhosted.org/packages/51/58/7f843608f2e8421f86bb97060b54649be6239ec612b82bf9d41e65c26c00/transformers-5.9.0.tar.gz", hash = "sha256:25997cb8fa6053533171634b6162d7df54346530ec2aa9b42bb834e63668c842", size = 8642240, upload-time = "2026-05-20T14:50:49.278Z" }
|
| 2899 |
wheels = [
|
| 2900 |
+
{ url = "https://files.pythonhosted.org/packages/02/ca/2eaa5359f2ccb8c2e1656bc26305ad0cf438aa392ce4b29ae67a315c186e/transformers-5.9.0-py3-none-any.whl", hash = "sha256:1d19509bcff7028ebc6b277d71caa712e8353778463d38764237d14b42b52788", size = 10787648, upload-time = "2026-05-20T14:50:45.337Z" },
|
| 2901 |
]
|
| 2902 |
|
| 2903 |
[[package]]
|