Spaces:
Paused
Backend: ephemeral flag on analyze stream — leave no cache trace
Browse filesThe What-if Rewrite & rerun re-uses /analyze/research/attention/stream
to regenerate text with an edited prompt. That endpoint
unconditionally called matrix_cache.clear_old_requests(new_id) on
every invocation, evicting the original traced run's matrices and
hidden states — so opening the Mechanism / Attention lenses
afterwards surfaced "Matrix data not found. Cache may have
expired."
Adds an opt-in ephemeral=true field on the stream request body.
When true, the handler skips every cache touch:
- clear_old_requests (keep prior runs intact)
- set_request_metadata
- set_step_query_info
- hidden_state_cache.store_step / store_input_ids
- matrix_cache.store (per-layer, per-head)
Default behaviour (no flag) is byte-identical to before, so every
existing caller — the main "Generate trace" path, the non-streaming
sibling, every other endpoint — is untouched. Only the What-if
regen opts in.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- backend/model_service.py +34 -17
|
@@ -2914,13 +2914,27 @@ async def analyze_research_attention_stream(request: Dict[str, Any], authenticat
|
|
| 2914 |
# Generate unique request ID for matrix cache lookup
|
| 2915 |
request_id = str(uuid.uuid4())
|
| 2916 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2917 |
# Clear old cached matrices to free memory before starting new analysis
|
| 2918 |
-
|
|
|
|
| 2919 |
|
| 2920 |
# Record per-request model metadata so aggregate-row lookups can
|
| 2921 |
# derive num_heads from this request rather than from the live
|
| 2922 |
# `manager.model` (which may have been switched in the meantime).
|
| 2923 |
-
if
|
|
|
|
|
|
|
|
|
|
| 2924 |
matrix_cache.set_request_metadata(
|
| 2925 |
request_id,
|
| 2926 |
num_heads=manager.adapter.get_num_heads(),
|
|
@@ -3305,9 +3319,10 @@ async def analyze_research_attention_stream(request: Dict[str, Any], authenticat
|
|
| 3305 |
# step's token (see non-SSE site for rationale).
|
| 3306 |
query_position = current_ids.shape[1] - 1
|
| 3307 |
query_token_id = current_ids[0, -1].item()
|
| 3308 |
-
|
| 3309 |
-
|
| 3310 |
-
|
|
|
|
| 3311 |
|
| 3312 |
# Get logits for next token
|
| 3313 |
raw_logits = outputs.logits[0, -1, :].clone() # Clone raw logits before any scaling
|
|
@@ -3594,12 +3609,13 @@ async def analyze_research_attention_stream(request: Dict[str, Any], authenticat
|
|
| 3594 |
})
|
| 3595 |
|
| 3596 |
# Cache hidden states, logits, and full sequence for intervention endpoint
|
| 3597 |
-
|
| 3598 |
-
|
| 3599 |
-
|
| 3600 |
-
|
| 3601 |
-
|
| 3602 |
-
|
|
|
|
| 3603 |
|
| 3604 |
# Emit generated token immediately so clients can show code progressively
|
| 3605 |
yield sse_event('generated_token', stage=2, totalStages=5,
|
|
@@ -3866,12 +3882,13 @@ async def analyze_research_attention_stream(request: Dict[str, Any], authenticat
|
|
| 3866 |
k_matrix = qkv_layer['k'][:, head_idx, :].float().numpy()
|
| 3867 |
v_matrix = qkv_layer['v'][:, head_idx, :].float().numpy()
|
| 3868 |
|
| 3869 |
-
|
| 3870 |
-
|
| 3871 |
-
|
| 3872 |
-
|
| 3873 |
-
|
| 3874 |
-
|
|
|
|
| 3875 |
|
| 3876 |
head_entry = {
|
| 3877 |
"head_idx": head_idx,
|
|
|
|
| 2914 |
# Generate unique request ID for matrix cache lookup
|
| 2915 |
request_id = str(uuid.uuid4())
|
| 2916 |
|
| 2917 |
+
# `ephemeral` callers (e.g. the What-if "Rewrite & rerun"
|
| 2918 |
+
# counterfactual) want their regen to coexist with the
|
| 2919 |
+
# original traced run rather than displace it — the user
|
| 2920 |
+
# still needs the original's matrices for the Mechanism /
|
| 2921 |
+
# Attention lenses. The new request_id is unique, so the
|
| 2922 |
+
# ephemeral run's own writes don't collide; we just have to
|
| 2923 |
+
# skip the eager `clear_old_requests` that would otherwise
|
| 2924 |
+
# purge every other request_id's cache.
|
| 2925 |
+
ephemeral = bool(request.get("ephemeral", False))
|
| 2926 |
+
|
| 2927 |
# Clear old cached matrices to free memory before starting new analysis
|
| 2928 |
+
if not ephemeral:
|
| 2929 |
+
matrix_cache.clear_old_requests(request_id)
|
| 2930 |
|
| 2931 |
# Record per-request model metadata so aggregate-row lookups can
|
| 2932 |
# derive num_heads from this request rather than from the live
|
| 2933 |
# `manager.model` (which may have been switched in the meantime).
|
| 2934 |
+
# Ephemeral runs (What-if Rewrite & rerun) skip every cache
|
| 2935 |
+
# write — the frontend discards everything except the
|
| 2936 |
+
# generated tokens, so the regen leaves no trace at all.
|
| 2937 |
+
if manager.adapter is not None and not ephemeral:
|
| 2938 |
matrix_cache.set_request_metadata(
|
| 2939 |
request_id,
|
| 2940 |
num_heads=manager.adapter.get_num_heads(),
|
|
|
|
| 3319 |
# step's token (see non-SSE site for rationale).
|
| 3320 |
query_position = current_ids.shape[1] - 1
|
| 3321 |
query_token_id = current_ids[0, -1].item()
|
| 3322 |
+
if not ephemeral:
|
| 3323 |
+
matrix_cache.set_step_query_info(
|
| 3324 |
+
request_id, step, query_position, query_token_id
|
| 3325 |
+
)
|
| 3326 |
|
| 3327 |
# Get logits for next token
|
| 3328 |
raw_logits = outputs.logits[0, -1, :].clone() # Clone raw logits before any scaling
|
|
|
|
| 3609 |
})
|
| 3610 |
|
| 3611 |
# Cache hidden states, logits, and full sequence for intervention endpoint
|
| 3612 |
+
if not ephemeral:
|
| 3613 |
+
try:
|
| 3614 |
+
hidden_state_cache.store_step(request_id, step, outputs.hidden_states, raw_logits, current_ids)
|
| 3615 |
+
if step == 0:
|
| 3616 |
+
hidden_state_cache.store_input_ids(request_id, current_ids[:, :-1]) # prompt only
|
| 3617 |
+
except Exception as hs_err:
|
| 3618 |
+
logger.debug(f"Hidden state cache error at step {step}: {hs_err}")
|
| 3619 |
|
| 3620 |
# Emit generated token immediately so clients can show code progressively
|
| 3621 |
yield sse_event('generated_token', stage=2, totalStages=5,
|
|
|
|
| 3882 |
k_matrix = qkv_layer['k'][:, head_idx, :].float().numpy()
|
| 3883 |
v_matrix = qkv_layer['v'][:, head_idx, :].float().numpy()
|
| 3884 |
|
| 3885 |
+
if not ephemeral:
|
| 3886 |
+
matrix_cache.store(request_id, step, layer_idx, head_idx, {
|
| 3887 |
+
"attention_weights": attention_matrix,
|
| 3888 |
+
"q_matrix": q_matrix,
|
| 3889 |
+
"k_matrix": k_matrix,
|
| 3890 |
+
"v_matrix": v_matrix
|
| 3891 |
+
})
|
| 3892 |
|
| 3893 |
head_entry = {
|
| 3894 |
"head_idx": head_idx,
|