psaiops 0.0.9__py3-none-any.whl → 0.0.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of psaiops might be problematic. Click here for more details.

@@ -10,7 +10,7 @@ import psaiops.score.attention.lib
10
10
 
11
11
  TITLE = '''Attention Scoring'''
12
12
  INTRO = '''Score each token according to the weights of the attention layers.\nThe model is fixed to "openai/gpt-oss-20b" for now.'''
13
- STYLE = ''''''
13
+ STYLE = '''.white-text span { color: white; }'''
14
14
 
15
15
  MODEL = 'openai/gpt-oss-20b'
16
16
 
@@ -19,21 +19,21 @@ MODEL = 'openai/gpt-oss-20b'
19
19
  def create_color_map() -> dict:
20
20
  return {
21
21
  '-1': '#00ff00',
22
- **{str(__i): '#{:02x}0000'.format(__i) for __i in range(101)}}
22
+ **{str(__i): '#{:02x}0000'.format(int(2.55 * __i)) for __i in range(101)}}
23
23
 
24
24
  # INTRO ########################################################################
25
25
 
26
26
  def create_intro_block(intro: str) -> dict:
27
- __intro = gradio.Markdown(intro)
27
+ __intro = gradio.Markdown(intro, scale=1)
28
28
  return {'intro_block': __intro}
29
29
 
30
30
  # MODEL ########################################################################
31
31
 
32
32
  def create_model_block() -> dict:
33
- __model_dd = gradio.Dropdown(label='Model', value='openai/gpt-oss-20b', choices=['openai/gpt-oss-20b'], allow_custom_value=False, multiselect=False, interactive=True) # 'openai/gpt-oss-120b'
34
- __layer_sl = gradio.Slider(label='Layer Depth', value=12, minimum=-1, maximum=23, step=1, interactive=True) # info='-1 to average on all layers'
35
- __head_sl = gradio.Slider(label='Attention Head', value=-1, minimum=-1, maximum=63, step=1, interactive=True) # info='-1 to average on all heads'
36
- __model_dd.change(fn=update_layer_range, inputs=[__layer_sl, __model_dd], outputs=__layer_sl, queue=False, show_progress='hidden')
33
+ __model_dd = gradio.Dropdown(label='Model', value='openai/gpt-oss-20b', choices=['openai/gpt-oss-20b'], scale=1, allow_custom_value=False, multiselect=False, interactive=True) # 'openai/gpt-oss-120b'
34
+ __layer_sl = gradio.Slider(label='Layer Depth', value=12, minimum=-1, maximum=23, step=1, scale=1, interactive=True) # info='-1 to average on all layers'
35
+ __head_sl = gradio.Slider(label='Attention Head', value=-1, minimum=-1, maximum=63, step=1, scale=1, interactive=True) # info='-1 to average on all heads'
36
+ __model_dd.change(fn=update_layer_range, inputs=[__layer_sl, __model_dd], outputs=__layer_sl, scale=1, queue=False, show_progress='hidden')
37
37
  return {
38
38
  'model_block': __model_dd,
39
39
  'layer_block': __layer_sl,
@@ -42,37 +42,43 @@ def create_model_block() -> dict:
42
42
  # SAMPLING #####################################################################
43
43
 
44
44
  def create_sampling_block() -> dict:
45
- __tokens = gradio.Slider(label='Tokens', value=32, minimum=0, maximum=128, step=1, interactive=True)
46
- __topk = gradio.Slider(label='Top K', value=4, minimum=1, maximum=8, step=1, interactive=True)
47
- __topp = gradio.Slider(label='Top P', value=0.8, minimum=0.0, maximum=1.0, step=0.1, interactive=True)
45
+ __tokens = gradio.Slider(label='Tokens', value=16, minimum=1, maximum=128, step=1, scale=1, interactive=True)
46
+ __topk = gradio.Slider(label='Top K', value=4, minimum=1, maximum=8, step=1, scale=1, interactive=True)
47
+ __topp = gradio.Slider(label='Top P', value=0.9, minimum=0.0, maximum=1.0, step=0.1, scale=1, interactive=True)
48
48
  return {
49
49
  'tokens_block': __tokens,
50
50
  'topk_block': __topk,
51
51
  'topp_block': __topp}
52
52
 
53
+ # TARGET #######################################################################
54
+
55
+ def create_target_block() -> dict:
56
+ __target = gradio.Radio(label='Score', value='Inputs', choices=['Inputs', 'Outputs', 'Both'], scale=1, interactive=True)
57
+ return {'target_block': __target}
58
+
53
59
  # DISPLAY ######################################################################
54
60
 
55
61
  def create_display_block() -> dict:
56
- __display = gradio.Radio(label='Display', value='Tokens', choices=['Tokens', 'Indexes'], interactive=True)
62
+ __display = gradio.Radio(label='Display', value='Tokens', choices=['Tokens', 'Indexes'], scale=1, interactive=True)
57
63
  return {'display_block': __display}
58
64
 
59
65
  # INPUTS #######################################################################
60
66
 
61
67
  def create_inputs_block() -> dict:
62
- __input = gradio.Textbox(label='Prompt', value='', placeholder='A string of tokens to score.', lines=4, show_copy_button=True, interactive=True)
68
+ __input = gradio.Textbox(label='Prompt', value='', placeholder='A string of tokens to score.', lines=4, scale=1, show_copy_button=True, interactive=True)
63
69
  return {'input_block': __input}
64
70
 
65
71
  # OUTPUTS ######################################################################
66
72
 
67
73
  def create_outputs_block() -> dict:
68
- __output = gradio.HighlightedText(label='Scores', value='', interactive=False, show_legend=False, show_inline_category=False, combine_adjacent=True, color_map=create_color_map())
74
+ __output = gradio.HighlightedText(label='Scores', value='', scale=1, interactive=False, show_legend=False, show_inline_category=False, combine_adjacent=True, color_map=create_color_map(), elem_classes='white-text')
69
75
  return {'output_block': __output}
70
76
 
71
77
  # ACTIONS ######################################################################
72
78
 
73
79
  def create_actions_block() -> dict:
74
- __process = gradio.Button('Process', variant='primary', size='lg', interactive=True)
75
- __position = gradio.Slider(label='Position', value=-1, minimum=-1, maximum=128, step=1, interactive=True) # info='-1 to average on all tokens'
80
+ __process = gradio.Button('Process', variant='primary', size='lg', scale=1, interactive=True)
81
+ __position = gradio.Slider(label='Position', value=-1, minimum=-1, maximum=128, step=1, scale=1, interactive=True) # info='-1 to average on all tokens'
76
82
  return {
77
83
  'process_block': __process,
78
84
  'position_block': __position}
@@ -80,7 +86,10 @@ def create_actions_block() -> dict:
80
86
  # STATE ########################################################################
81
87
 
82
88
  def create_state() -> dict:
83
- return {'attention_state': gradio.State(None), 'token_state': gradio.State(None)}
89
+ return {
90
+ 'input_state': gradio.State(None),
91
+ 'output_state': gradio.State(None),
92
+ 'attention_state': gradio.State(None),}
84
93
 
85
94
  # LAYOUT #######################################################################
86
95
 
@@ -90,36 +99,121 @@ def create_layout(intro: str=INTRO) -> dict:
90
99
  with gradio.Tabs():
91
100
  with gradio.Tab('Score Tokens') as __main_tab:
92
101
  __fields.update({'main_tab': __main_tab})
93
- with gradio.Row():
94
- with gradio.Column(scale=1):
95
- __fields.update(create_inputs_block())
96
- with gradio.Column(scale=1):
97
- __fields.update(create_outputs_block())
98
- with gradio.Row():
102
+ with gradio.Row(equal_height=True):
103
+ __fields.update(create_inputs_block())
104
+ __fields.update(create_outputs_block())
105
+ with gradio.Row(equal_height=True):
99
106
  __fields.update(create_actions_block())
100
107
  with gradio.Tab('Settings') as __settings_tab:
101
108
  __fields.update({'settings_tab': __settings_tab})
102
109
  with gradio.Column(scale=1):
103
- with gradio.Row():
110
+ with gradio.Row(equal_height=True):
104
111
  __fields.update(create_model_block())
105
- with gradio.Row():
112
+ with gradio.Row(equal_height=True):
106
113
  __fields.update(create_sampling_block())
107
- with gradio.Row():
114
+ with gradio.Row(equal_height=True):
115
+ __fields.update(create_target_block())
108
116
  __fields.update(create_display_block())
109
117
  return __fields
110
118
 
111
119
  # EVENTS #######################################################################
112
120
 
113
- def update_layer_range(value: int, model: str) -> dict:
121
+ def update_layer_range(value: float, model: str) -> dict:
114
122
  return gradio.update(maximum=35, value=min(35, int(value))) if '120b' in model else gradio.update(maximum=23, value=min(23, int(value)))
115
123
 
116
- def update_position_range(value: int, dimension: int) -> dict:
117
- return gradio.update(maximum=dimension - 1, value=min(dimension - 1, value))
118
-
119
- def update_output_value(
120
- attention_data: torch.Tensor=None,
121
- token_data: torch.Tensor=None,) -> torch.Tensor:
122
- return
124
+ def update_position_range(value: float, tokens: list) -> dict:
125
+ return gradio.update(maximum=len(tokens) - 1, value=min(len(tokens) - 1, int(value)))
126
+
127
+ def update_computation_state(
128
+ token_num: float,
129
+ topk_num: float,
130
+ topp_num: float,
131
+ prompt_str: str,
132
+ device_str: str,
133
+ model_obj: object,
134
+ tokenizer_obj: object,
135
+ ) -> tuple:
136
+ # sanitize the inputs
137
+ __limit = max(1, min(128, int(token_num)))
138
+ __topk = max(1, min(128, int(token_num)))
139
+ __topp = max(0.0, min(1.0, float(token_num)))
140
+ __prompt = prompt_str.strip()
141
+ __device = device_str if (device_str in ['cpu', 'cuda']) else 'cpu'
142
+ # handle all exceptions at once
143
+ try:
144
+ # dictionary {'input_ids': _, 'attention_mask': _}
145
+ __inputs = psaiops.score.attention.lib.preprocess_token_ids(
146
+ tokenizer_obj=tokenizer_obj,
147
+ prompt_str=__prompt,
148
+ device_str=__device)
149
+ # parse the inputs
150
+ __input_dim = int(__inputs['input_ids'].shape[-1])
151
+ # tensor (1, T)
152
+ __outputs = psaiops.score.attention.lib.generate_token_ids(
153
+ model_obj=model_obj,
154
+ input_args=__inputs,
155
+ token_num=__limit,
156
+ topk_num=__topk,
157
+ topp_num=__topp)
158
+ # tensor (L, S, H, T, T)
159
+ __attentions = psaiops.score.attention.lib.compute_attention_weights(
160
+ model_obj=model_obj,
161
+ token_obj=__outputs)
162
+ # detokenize the IDs
163
+ __tokens = psaiops.score.attention.lib.postprocess_token_ids(
164
+ tokenizer_obj=tokenizer_obj,
165
+ token_obj=__outputs)
166
+ # update each component => (input, output, attention) states
167
+ return (
168
+ gradio.update(value=__tokens[:__input_dim]),
169
+ gradio.update(value=__tokens[__input_dim:]),
170
+ gradio.update(value=__attentions),)
171
+ except:
172
+ raise Exception('Attention generation aborted with an error.')
173
+ finally:
174
+ return (gradio.update(), gradio.update(), gradio.update())
175
+
176
+ def update_text_highlight(
177
+ token_idx: float,
178
+ layer_idx: float,
179
+ head_idx: float,
180
+ input_data: list,
181
+ output_data: list,
182
+ attention_data: torch.Tensor,
183
+ ) -> dict:
184
+ # sanitize the inputs
185
+ __input_data = input_data or []
186
+ __output_data = output_data or []
187
+ __attention_data = attention_data or torch.empty(0)
188
+ __input_dim = len(__input_data)
189
+ __token_idx = max(0, min(__input_dim, int(token_idx)))
190
+ __layer_idx = max(0, int(layer_idx))
191
+ __head_idx = max(0, int(head_idx))
192
+ # exit if the data has not yet been computed
193
+ if (not __input_data) or (not __output_data) or (attention_data is None) or (len(attention_data) == 0):
194
+ return gradio.update()
195
+ # handle all exceptions at once
196
+ try:
197
+ # concat input and output tokens
198
+ __tokens = __input_data + __output_data
199
+ # reduce the layer, sample, head and output token axes => tensor (T,)
200
+ __scores = psaiops.score.attention.lib.reduce_attention_weights(
201
+ attention_data=__attention_data,
202
+ token_idx=__token_idx,
203
+ layer_idx=__layer_idx,
204
+ head_idx=__head_idx,
205
+ input_dim=__input_dim)
206
+ # translate the scores into integer labels
207
+ __labels = psaiops.score.attention.lib.postprocess_attention_scores(
208
+ attention_data=__scores,
209
+ input_dim=__input_dim,
210
+ token_idx=__token_idx)
211
+ # update the component with [(token, label), ...]
212
+ return gradio.update(value=list(zip(__tokens, __labels)))
213
+ except:
214
+ raise Exception('Attention reduction aborted with an error.')
215
+ finally:
216
+ return gradio.update()
123
217
 
124
218
  # APP ##########################################################################
125
219
 
@@ -130,21 +224,40 @@ def create_app(title: str=TITLE, intro: str=INTRO, style: str=STYLE, model: str=
130
224
  __device = 'cuda' if torch.cuda.is_available() else 'cpu'
131
225
  __model = psaiops.score.attention.lib.get_model(name=model, device=__device)
132
226
  __tokenizer = psaiops.score.attention.lib.get_tokenizer(name=model, device=__device)
133
- # adapt the scoring function
134
- __score = functools.partial(psaiops.score.attention.lib.score_tokens, model_obj=__model, tokenizer_obj=__tokenizer, device_str=__device)
227
+ # adapt the computing function
228
+ __compute = functools.partial(update_computation_state, model_obj=__model, tokenizer_obj=__tokenizer, device_str=__device)
135
229
  # create the UI
136
230
  __fields.update(create_layout(intro=intro))
137
231
  # init the state
138
232
  __fields.update(create_state())
139
233
  # fetch the relevant fields
140
- __button = __fields['process_block']
234
+ __button_block, __position_block, __output_block = (__fields['process_block'], __fields['position_block'], __fields['output_block'])
235
+ __output_state, __attention_state = (__fields['output_state'], __fields['attention_state'])
141
236
  # wire the input fields
142
- __button.click(
143
- fn=__score,
144
- inputs=[__fields[__k] for __k in ['input_block', 'tokens_block', 'topk_block', 'topp_block', 'position_block', 'layer_block', 'head_block']],
145
- outputs=__fields['output_block'],
237
+ __button_block.click(
238
+ fn=__compute,
239
+ inputs=[__fields[__k] for __k in ['tokens_block', 'topk_block', 'topp_block', 'input_block']],
240
+ outputs=[__fields[__k] for __k in ['input_state', 'output_state', 'attention_state']],
146
241
  queue=False,
147
242
  show_progress='full')
243
+ __output_state.change(
244
+ fn=update_position_range,
245
+ inputs=[__position_block, __output],
246
+ outputs=__position_block,
247
+ queue=False,
248
+ show_progress='hidden')
249
+ __attention_state.change(
250
+ fn=update_text_highlight,
251
+ inputs=[__fields[__k] for __k in ['position_block', 'layer_block', 'head_block', 'input_state', 'output_state', 'attention_state']],
252
+ outputs=__output_block,
253
+ queue=False,
254
+ show_progress='hidden')
255
+ __position_block.change(
256
+ fn=update_text_highlight,
257
+ inputs=[__fields[__k] for __k in ['position_block', 'layer_block', 'head_block', 'input_state', 'output_state', 'attention_state']],
258
+ outputs=__output_block,
259
+ queue=False,
260
+ show_progress='hidden')
148
261
  # gradio application
149
262
  return __app
150
263
 
@@ -28,6 +28,7 @@ def get_model(name: str, device: str='cpu'):
28
28
 
29
29
  # PREPROCESS #####################################################################
30
30
 
31
+ @functools.lru_cache(maxsize=4)
31
32
  def preprocess_token_ids(
32
33
  tokenizer_obj: object,
33
34
  prompt_str: str,
@@ -137,7 +138,7 @@ def postprocess_token_ids(
137
138
  # back to token strings
138
139
  __tokens = tokenizer_obj.convert_ids_to_tokens(__indices)
139
140
  # normalize the tokens
140
- return [__t.replace('Ġ', ' ') for __t in __tokens]
141
+ return [__t.replace(chr(0x0120), ' ').replace(chr(0x010a), '\n') for __t in __tokens]
141
142
 
142
143
  # COMPUTE ########################################################################
143
144
 
@@ -149,7 +150,6 @@ def score_tokens(
149
150
  token_idx: int,
150
151
  layer_idx: int,
151
152
  head_idx: int,
152
- *,
153
153
  device_str: str,
154
154
  model_obj: object,
155
155
  tokenizer_obj: object,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: psaiops
3
- Version: 0.0.9
3
+ Version: 0.0.11
4
4
  Summary: Web apps to inspect & engineer NN activations.
5
5
  License: .github/LICENSE.md
6
6
  Author: apehex
@@ -7,9 +7,9 @@ psaiops/elements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
7
7
  psaiops/elements/data.py,sha256=vGYeMN11uP9gs8rV6aSDffE_TeIX5PmdzWGwUpdGE2Y,906
8
8
  psaiops/score/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  psaiops/score/attention/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- psaiops/score/attention/app.py,sha256=E4XKNXEGwcL5ceOVy6twd88IeFoR6RHxZ5C4Arn9jhM,6973
11
- psaiops/score/attention/lib.py,sha256=vWm8e_xUkM4ahgYQfaNK_kpU9eGkKAGHfgA-Sm1htmc,6869
10
+ psaiops/score/attention/app.py,sha256=SS-cQ45HAv03rxGjzRNCV5BIfyP_Qry58bJ0b3OIxZU,11979
11
+ psaiops/score/attention/lib.py,sha256=kPLdmWCYbpMnd7fi4ZeCvxiGzJuvTKYSXgRAm_w02KA,6928
12
12
  psaiops/steer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- psaiops-0.0.9.dist-info/METADATA,sha256=RiCvOFOoW8q4vUzNngllFcUhC-QeiDD6_TPiy8wSOEA,1221
14
- psaiops-0.0.9.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
15
- psaiops-0.0.9.dist-info/RECORD,,
13
+ psaiops-0.0.11.dist-info/METADATA,sha256=s3jjhNDNG0Zm-pBoxrkuznmkuxJr-04Dxj6MbFsCNIA,1222
14
+ psaiops-0.0.11.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
15
+ psaiops-0.0.11.dist-info/RECORD,,