gary-boon Claude Opus 4.7 (1M context) commited on
Commit
88879ce
·
1 Parent(s): f578686

Backend: ephemeral flag on analyze stream — leave no cache trace

Browse files

The 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>

Files changed (1) hide show
  1. backend/model_service.py +34 -17
backend/model_service.py CHANGED
@@ -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
- matrix_cache.clear_old_requests(request_id)
 
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 manager.adapter is not None:
 
 
 
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
- matrix_cache.set_step_query_info(
3309
- request_id, step, query_position, query_token_id
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
- try:
3598
- hidden_state_cache.store_step(request_id, step, outputs.hidden_states, raw_logits, current_ids)
3599
- if step == 0:
3600
- hidden_state_cache.store_input_ids(request_id, current_ids[:, :-1]) # prompt only
3601
- except Exception as hs_err:
3602
- logger.debug(f"Hidden state cache error at step {step}: {hs_err}")
 
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
- matrix_cache.store(request_id, step, layer_idx, head_idx, {
3870
- "attention_weights": attention_matrix,
3871
- "q_matrix": q_matrix,
3872
- "k_matrix": k_matrix,
3873
- "v_matrix": v_matrix
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,