Update app.py
Browse files
app.py
CHANGED
|
@@ -102,6 +102,29 @@ GEN_TOP_K = int(_gen_cfg.get('top_k', 1))
|
|
| 102 |
GEN_REP_PENALTY = float(_gen_cfg.get('repetition_penalty', 1.0))
|
| 103 |
STEER_DISPLAY_K = 10 # top-k candidates shown in the per-token probability panel
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
# βββ Device resolution βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 106 |
|
| 107 |
def _resolve_sae_device() -> torch.device:
|
|
@@ -502,18 +525,19 @@ def _steering_strength_from_mode(mode: str, diff_lookup, layer: int, feat_idx: i
|
|
| 502 |
|
| 503 |
def cb_generate(prompt, layer, feat_idx, pos_str, steer_mode, compare_diff,
|
| 504 |
steer_output_only, max_tok, greedy, top_k_tok, top_p, rep_penalty, temp,
|
| 505 |
-
custom_strength=5.0):
|
| 506 |
try:
|
| 507 |
return _cb_generate_inner(prompt, layer, feat_idx, pos_str, steer_mode, compare_diff,
|
| 508 |
steer_output_only, max_tok, greedy, top_k_tok, top_p, rep_penalty, temp,
|
| 509 |
-
custom_strength)
|
| 510 |
except gr.Error:
|
| 511 |
raise
|
| 512 |
except Exception as e:
|
| 513 |
raise gr.Error(f"Generation failed: {e}")
|
| 514 |
|
| 515 |
|
| 516 |
-
def cb_update_steer_preview(prompt: str, pos_str: str
|
|
|
|
| 517 |
"""Tokenise the prompt and return an HTML token-position preview."""
|
| 518 |
if not prompt.strip():
|
| 519 |
return (
|
|
@@ -522,7 +546,13 @@ def cb_update_steer_preview(prompt: str, pos_str: str):
|
|
| 522 |
)
|
| 523 |
try:
|
| 524 |
_, tokenizer = get_model()
|
| 525 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
tokens = [tokenizer.decode([t]) for t in input_ids]
|
| 527 |
positions = parse_positions(pos_str)
|
| 528 |
return tokens_with_positions_html(tokens, positions)
|
|
@@ -706,7 +736,7 @@ def probs_to_html(tokens: list, chosen_probs: list, topk_data: list,
|
|
| 706 |
|
| 707 |
def _cb_generate_inner(prompt, layer, feat_idx, pos_str, steer_mode, compare_diff,
|
| 708 |
steer_output_only, max_tok, greedy, top_k_tok, top_p, rep_penalty, temp,
|
| 709 |
-
custom_strength=5.0):
|
| 710 |
global _orig_cache
|
| 711 |
model, tokenizer = get_model()
|
| 712 |
layer = int(layer)
|
|
@@ -716,7 +746,13 @@ def _cb_generate_inner(prompt, layer, feat_idx, pos_str, steer_mode, compare_dif
|
|
| 716 |
strength = _steering_strength_from_mode(steer_mode, compare_diff, layer, feat_idx, float(custom_strength))
|
| 717 |
positions = parse_positions(pos_str)
|
| 718 |
|
| 719 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 720 |
next(model.parameters()).device
|
| 721 |
)
|
| 722 |
|
|
@@ -738,9 +774,9 @@ def _cb_generate_inner(prompt, layer, feat_idx, pos_str, steer_mode, compare_dif
|
|
| 738 |
# The unsteered output depends only on the prompt and decoding parameters,
|
| 739 |
# not on any steering inputs. Reuse the last result when those are unchanged.
|
| 740 |
if greedy:
|
| 741 |
-
orig_key = (
|
| 742 |
else:
|
| 743 |
-
orig_key = (
|
| 744 |
int(top_k_tok), float(top_p), float(rep_penalty), float(temp))
|
| 745 |
|
| 746 |
if _orig_cache is not None and _orig_cache['key'] == orig_key:
|
|
@@ -1495,6 +1531,24 @@ with gr.Blocks(title="Qwen-Scope Feature Explorer") as demo:
|
|
| 1495 |
show_label=False,
|
| 1496 |
)
|
| 1497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1498 |
gr.HTML('<span class="section-chip">Token Position Preview</span>'
|
| 1499 |
'<span style="font-size:12px;color:#888;margin-left:8px;">'
|
| 1500 |
'amber = steered Β· updates as you type'
|
|
@@ -1676,7 +1730,7 @@ with gr.Blocks(title="Qwen-Scope Feature Explorer") as demo:
|
|
| 1676 |
inputs=[t2_prompt, t2_layer, t2_feat, t2_pos, t2_steer_mode, compare_diff_state,
|
| 1677 |
t2_steer_output_only, t2_maxtok,
|
| 1678 |
t2_greedy, t2_top_k_tok, t2_top_p, t2_rep_penalty,
|
| 1679 |
-
t2_temperature, t2_custom_strength],
|
| 1680 |
outputs=[t2_orig, t2_steered, t2_orig_probs, t2_steer_probs],
|
| 1681 |
)
|
| 1682 |
t3_run.click(
|
|
@@ -1699,12 +1753,12 @@ with gr.Blocks(title="Qwen-Scope Feature Explorer") as demo:
|
|
| 1699 |
)
|
| 1700 |
t2_prompt.change(
|
| 1701 |
cb_update_steer_preview,
|
| 1702 |
-
inputs=[t2_prompt, t2_pos],
|
| 1703 |
outputs=[t2_pos_preview],
|
| 1704 |
)
|
| 1705 |
t2_pos.change(
|
| 1706 |
cb_update_steer_preview,
|
| 1707 |
-
inputs=[t2_prompt, t2_pos],
|
| 1708 |
outputs=[t2_pos_preview],
|
| 1709 |
)
|
| 1710 |
|
|
@@ -1763,6 +1817,69 @@ with gr.Blocks(title="Qwen-Scope Feature Explorer") as demo:
|
|
| 1763 |
outputs=[t2_custom_strength],
|
| 1764 |
)
|
| 1765 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1766 |
|
| 1767 |
if __name__ == '__main__':
|
| 1768 |
# Pre-load model onto GPU before accepting requests, so the first
|
|
|
|
| 102 |
GEN_REP_PENALTY = float(_gen_cfg.get('repetition_penalty', 1.0))
|
| 103 |
STEER_DISPLAY_K = 10 # top-k candidates shown in the per-token probability panel
|
| 104 |
|
| 105 |
+
# βββ Default chat templates (thinking / no-thinking) βββββββββββββββββββββββββ
|
| 106 |
+
|
| 107 |
+
_THINK_TEMPLATE = (
|
| 108 |
+
"<|im_start|>user\n"
|
| 109 |
+
"{content}"
|
| 110 |
+
"<|im_end|>\n"
|
| 111 |
+
"<|im_start|>assistant\n"
|
| 112 |
+
"<think>\n"
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
_NOTHINK_TEMPLATE = (
|
| 116 |
+
"<|im_start|>user\n"
|
| 117 |
+
"{content}"
|
| 118 |
+
"<|im_end|>\n"
|
| 119 |
+
"<|im_start|>assistant\n"
|
| 120 |
+
"<think>\n\n</think>\n\n"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
def apply_default_template(prompt: str, think: bool) -> str:
|
| 124 |
+
"""Wrap *prompt* in the ChatML template for thinking or no-thinking mode."""
|
| 125 |
+
tpl = _THINK_TEMPLATE if think else _NOTHINK_TEMPLATE
|
| 126 |
+
return tpl.format(content=prompt.strip())
|
| 127 |
+
|
| 128 |
# βββ Device resolution βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 129 |
|
| 130 |
def _resolve_sae_device() -> torch.device:
|
|
|
|
| 525 |
|
| 526 |
def cb_generate(prompt, layer, feat_idx, pos_str, steer_mode, compare_diff,
|
| 527 |
steer_output_only, max_tok, greedy, top_k_tok, top_p, rep_penalty, temp,
|
| 528 |
+
custom_strength=5.0, apply_think=False, apply_nothink=False):
|
| 529 |
try:
|
| 530 |
return _cb_generate_inner(prompt, layer, feat_idx, pos_str, steer_mode, compare_diff,
|
| 531 |
steer_output_only, max_tok, greedy, top_k_tok, top_p, rep_penalty, temp,
|
| 532 |
+
custom_strength, apply_think, apply_nothink)
|
| 533 |
except gr.Error:
|
| 534 |
raise
|
| 535 |
except Exception as e:
|
| 536 |
raise gr.Error(f"Generation failed: {e}")
|
| 537 |
|
| 538 |
|
| 539 |
+
def cb_update_steer_preview(prompt: str, pos_str: str,
|
| 540 |
+
apply_think: bool = False, apply_nothink: bool = False):
|
| 541 |
"""Tokenise the prompt and return an HTML token-position preview."""
|
| 542 |
if not prompt.strip():
|
| 543 |
return (
|
|
|
|
| 546 |
)
|
| 547 |
try:
|
| 548 |
_, tokenizer = get_model()
|
| 549 |
+
if apply_think:
|
| 550 |
+
effective = apply_default_template(prompt, think=True)
|
| 551 |
+
elif apply_nothink:
|
| 552 |
+
effective = apply_default_template(prompt, think=False)
|
| 553 |
+
else:
|
| 554 |
+
effective = prompt
|
| 555 |
+
input_ids = tokenizer.encode(effective)
|
| 556 |
tokens = [tokenizer.decode([t]) for t in input_ids]
|
| 557 |
positions = parse_positions(pos_str)
|
| 558 |
return tokens_with_positions_html(tokens, positions)
|
|
|
|
| 736 |
|
| 737 |
def _cb_generate_inner(prompt, layer, feat_idx, pos_str, steer_mode, compare_diff,
|
| 738 |
steer_output_only, max_tok, greedy, top_k_tok, top_p, rep_penalty, temp,
|
| 739 |
+
custom_strength=5.0, apply_think=False, apply_nothink=False):
|
| 740 |
global _orig_cache
|
| 741 |
model, tokenizer = get_model()
|
| 742 |
layer = int(layer)
|
|
|
|
| 746 |
strength = _steering_strength_from_mode(steer_mode, compare_diff, layer, feat_idx, float(custom_strength))
|
| 747 |
positions = parse_positions(pos_str)
|
| 748 |
|
| 749 |
+
if apply_think:
|
| 750 |
+
effective_prompt = apply_default_template(prompt, think=True)
|
| 751 |
+
elif apply_nothink:
|
| 752 |
+
effective_prompt = apply_default_template(prompt, think=False)
|
| 753 |
+
else:
|
| 754 |
+
effective_prompt = prompt
|
| 755 |
+
input_ids = tokenizer.encode(effective_prompt, return_tensors='pt').to(
|
| 756 |
next(model.parameters()).device
|
| 757 |
)
|
| 758 |
|
|
|
|
| 774 |
# The unsteered output depends only on the prompt and decoding parameters,
|
| 775 |
# not on any steering inputs. Reuse the last result when those are unchanged.
|
| 776 |
if greedy:
|
| 777 |
+
orig_key = (effective_prompt, int(max_tok), True)
|
| 778 |
else:
|
| 779 |
+
orig_key = (effective_prompt, int(max_tok), False,
|
| 780 |
int(top_k_tok), float(top_p), float(rep_penalty), float(temp))
|
| 781 |
|
| 782 |
if _orig_cache is not None and _orig_cache['key'] == orig_key:
|
|
|
|
| 1531 |
show_label=False,
|
| 1532 |
)
|
| 1533 |
|
| 1534 |
+
t2_apply_think = gr.Checkbox(
|
| 1535 |
+
label="Apply default thinking template",
|
| 1536 |
+
value=False,
|
| 1537 |
+
info=(
|
| 1538 |
+
"Wrap the prompt in the ChatML format with thinking enabled "
|
| 1539 |
+
"(assistant prefill starts with <think>)."
|
| 1540 |
+
),
|
| 1541 |
+
)
|
| 1542 |
+
t2_apply_nothink = gr.Checkbox(
|
| 1543 |
+
label="Apply default no-thinking template",
|
| 1544 |
+
value=False,
|
| 1545 |
+
info=(
|
| 1546 |
+
"Wrap the prompt in the ChatML format with thinking disabled "
|
| 1547 |
+
"(assistant prefill starts with <think>\\n\\n</think>)."
|
| 1548 |
+
),
|
| 1549 |
+
)
|
| 1550 |
+
t2_template_info = gr.HTML(visible=False, value="")
|
| 1551 |
+
|
| 1552 |
gr.HTML('<span class="section-chip">Token Position Preview</span>'
|
| 1553 |
'<span style="font-size:12px;color:#888;margin-left:8px;">'
|
| 1554 |
'amber = steered Β· updates as you type'
|
|
|
|
| 1730 |
inputs=[t2_prompt, t2_layer, t2_feat, t2_pos, t2_steer_mode, compare_diff_state,
|
| 1731 |
t2_steer_output_only, t2_maxtok,
|
| 1732 |
t2_greedy, t2_top_k_tok, t2_top_p, t2_rep_penalty,
|
| 1733 |
+
t2_temperature, t2_custom_strength, t2_apply_think, t2_apply_nothink],
|
| 1734 |
outputs=[t2_orig, t2_steered, t2_orig_probs, t2_steer_probs],
|
| 1735 |
)
|
| 1736 |
t3_run.click(
|
|
|
|
| 1753 |
)
|
| 1754 |
t2_prompt.change(
|
| 1755 |
cb_update_steer_preview,
|
| 1756 |
+
inputs=[t2_prompt, t2_pos, t2_apply_think, t2_apply_nothink],
|
| 1757 |
outputs=[t2_pos_preview],
|
| 1758 |
)
|
| 1759 |
t2_pos.change(
|
| 1760 |
cb_update_steer_preview,
|
| 1761 |
+
inputs=[t2_prompt, t2_pos, t2_apply_think, t2_apply_nothink],
|
| 1762 |
outputs=[t2_pos_preview],
|
| 1763 |
)
|
| 1764 |
|
|
|
|
| 1817 |
outputs=[t2_custom_strength],
|
| 1818 |
)
|
| 1819 |
|
| 1820 |
+
# ββ Template toggle: mutual exclusion + info panel + preview refresh β
|
| 1821 |
+
_THINK_INFO_HTML = (
|
| 1822 |
+
'<div style="font-size:11px;color:#555;padding:6px 10px;'
|
| 1823 |
+
'background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;'
|
| 1824 |
+
'font-family:ui-monospace,monospace;white-space:pre-wrap;line-height:1.7;">'
|
| 1825 |
+
'<|im_start|>user\n'
|
| 1826 |
+
'{your prompt}<|im_end|>\n'
|
| 1827 |
+
'<|im_start|>assistant\n'
|
| 1828 |
+
'<think>\n'
|
| 1829 |
+
'</div>'
|
| 1830 |
+
)
|
| 1831 |
+
_NOTHINK_INFO_HTML = (
|
| 1832 |
+
'<div style="font-size:11px;color:#555;padding:6px 10px;'
|
| 1833 |
+
'background:#f0fdf4;border:1px solid #bbf7d0;border-radius:6px;'
|
| 1834 |
+
'font-family:ui-monospace,monospace;white-space:pre-wrap;line-height:1.7;">'
|
| 1835 |
+
'<|im_start|>user\n'
|
| 1836 |
+
'{your prompt}<|im_end|>\n'
|
| 1837 |
+
'<|im_start|>assistant\n'
|
| 1838 |
+
'<think>\n\n</think>\n\n'
|
| 1839 |
+
'</div>'
|
| 1840 |
+
)
|
| 1841 |
+
|
| 1842 |
+
def _on_think_change(think_val, nothink_val, prompt, pos_str):
|
| 1843 |
+
if think_val:
|
| 1844 |
+
# Just checked: uncheck nothink, show think format, refresh preview
|
| 1845 |
+
return (gr.update(value=False),
|
| 1846 |
+
gr.update(visible=True, value=_THINK_INFO_HTML),
|
| 1847 |
+
cb_update_steer_preview(prompt, pos_str, True, False))
|
| 1848 |
+
elif nothink_val:
|
| 1849 |
+
# Unchecked by mutual exclusion β nothink is active; leave info+preview alone
|
| 1850 |
+
return gr.update(), gr.update(), gr.update()
|
| 1851 |
+
else:
|
| 1852 |
+
# Manually unchecked with nothing active β reset to raw
|
| 1853 |
+
return (gr.update(),
|
| 1854 |
+
gr.update(visible=False),
|
| 1855 |
+
cb_update_steer_preview(prompt, pos_str, False, False))
|
| 1856 |
+
|
| 1857 |
+
def _on_nothink_change(nothink_val, think_val, prompt, pos_str):
|
| 1858 |
+
if nothink_val:
|
| 1859 |
+
# Just checked: uncheck think, show nothink format, refresh preview
|
| 1860 |
+
return (gr.update(value=False),
|
| 1861 |
+
gr.update(visible=True, value=_NOTHINK_INFO_HTML),
|
| 1862 |
+
cb_update_steer_preview(prompt, pos_str, False, True))
|
| 1863 |
+
elif think_val:
|
| 1864 |
+
# Unchecked by mutual exclusion β think is active; leave info+preview alone
|
| 1865 |
+
return gr.update(), gr.update(), gr.update()
|
| 1866 |
+
else:
|
| 1867 |
+
# Manually unchecked with nothing active β reset to raw
|
| 1868 |
+
return (gr.update(),
|
| 1869 |
+
gr.update(visible=False),
|
| 1870 |
+
cb_update_steer_preview(prompt, pos_str, False, False))
|
| 1871 |
+
|
| 1872 |
+
t2_apply_think.change(
|
| 1873 |
+
fn=_on_think_change,
|
| 1874 |
+
inputs=[t2_apply_think, t2_apply_nothink, t2_prompt, t2_pos],
|
| 1875 |
+
outputs=[t2_apply_nothink, t2_template_info, t2_pos_preview],
|
| 1876 |
+
)
|
| 1877 |
+
t2_apply_nothink.change(
|
| 1878 |
+
fn=_on_nothink_change,
|
| 1879 |
+
inputs=[t2_apply_nothink, t2_apply_think, t2_prompt, t2_pos],
|
| 1880 |
+
outputs=[t2_apply_think, t2_template_info, t2_pos_preview],
|
| 1881 |
+
)
|
| 1882 |
+
|
| 1883 |
|
| 1884 |
if __name__ == '__main__':
|
| 1885 |
# Pre-load model onto GPU before accepting requests, so the first
|