psaiops 0.0.10__tar.gz → 0.0.11__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: psaiops
3
- Version: 0.0.10
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
@@ -0,0 +1,268 @@
1
+ import functools
2
+
3
+ import gradio
4
+ import torch
5
+ import torch.cuda
6
+
7
+ import psaiops.score.attention.lib
8
+
9
+ # META #########################################################################
10
+
11
+ TITLE = '''Attention Scoring'''
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 = '''.white-text span { color: white; }'''
14
+
15
+ MODEL = 'openai/gpt-oss-20b'
16
+
17
+ # COLORS #######################################################################
18
+
19
+ def create_color_map() -> dict:
20
+ return {
21
+ '-1': '#00ff00',
22
+ **{str(__i): '#{:02x}0000'.format(int(2.55 * __i)) for __i in range(101)}}
23
+
24
+ # INTRO ########################################################################
25
+
26
+ def create_intro_block(intro: str) -> dict:
27
+ __intro = gradio.Markdown(intro, scale=1)
28
+ return {'intro_block': __intro}
29
+
30
+ # MODEL ########################################################################
31
+
32
+ def create_model_block() -> dict:
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
+ return {
38
+ 'model_block': __model_dd,
39
+ 'layer_block': __layer_sl,
40
+ 'head_block': __head_sl}
41
+
42
+ # SAMPLING #####################################################################
43
+
44
+ def create_sampling_block() -> dict:
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
+ return {
49
+ 'tokens_block': __tokens,
50
+ 'topk_block': __topk,
51
+ 'topp_block': __topp}
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
+
59
+ # DISPLAY ######################################################################
60
+
61
+ def create_display_block() -> dict:
62
+ __display = gradio.Radio(label='Display', value='Tokens', choices=['Tokens', 'Indexes'], scale=1, interactive=True)
63
+ return {'display_block': __display}
64
+
65
+ # INPUTS #######################################################################
66
+
67
+ def create_inputs_block() -> dict:
68
+ __input = gradio.Textbox(label='Prompt', value='', placeholder='A string of tokens to score.', lines=4, scale=1, show_copy_button=True, interactive=True)
69
+ return {'input_block': __input}
70
+
71
+ # OUTPUTS ######################################################################
72
+
73
+ def create_outputs_block() -> dict:
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')
75
+ return {'output_block': __output}
76
+
77
+ # ACTIONS ######################################################################
78
+
79
+ def create_actions_block() -> dict:
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'
82
+ return {
83
+ 'process_block': __process,
84
+ 'position_block': __position}
85
+
86
+ # STATE ########################################################################
87
+
88
+ def create_state() -> dict:
89
+ return {
90
+ 'input_state': gradio.State(None),
91
+ 'output_state': gradio.State(None),
92
+ 'attention_state': gradio.State(None),}
93
+
94
+ # LAYOUT #######################################################################
95
+
96
+ def create_layout(intro: str=INTRO) -> dict:
97
+ __fields = {}
98
+ __fields.update(create_intro_block(intro=intro))
99
+ with gradio.Tabs():
100
+ with gradio.Tab('Score Tokens') as __main_tab:
101
+ __fields.update({'main_tab': __main_tab})
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):
106
+ __fields.update(create_actions_block())
107
+ with gradio.Tab('Settings') as __settings_tab:
108
+ __fields.update({'settings_tab': __settings_tab})
109
+ with gradio.Column(scale=1):
110
+ with gradio.Row(equal_height=True):
111
+ __fields.update(create_model_block())
112
+ with gradio.Row(equal_height=True):
113
+ __fields.update(create_sampling_block())
114
+ with gradio.Row(equal_height=True):
115
+ __fields.update(create_target_block())
116
+ __fields.update(create_display_block())
117
+ return __fields
118
+
119
+ # EVENTS #######################################################################
120
+
121
+ def update_layer_range(value: float, model: str) -> dict:
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)))
123
+
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()
217
+
218
+ # APP ##########################################################################
219
+
220
+ def create_app(title: str=TITLE, intro: str=INTRO, style: str=STYLE, model: str=MODEL) -> gradio.Blocks:
221
+ __fields = {}
222
+ with gradio.Blocks(theme=gradio.themes.Soft(), title=title, css=style) as __app:
223
+ # load the model
224
+ __device = 'cuda' if torch.cuda.is_available() else 'cpu'
225
+ __model = psaiops.score.attention.lib.get_model(name=model, device=__device)
226
+ __tokenizer = psaiops.score.attention.lib.get_tokenizer(name=model, device=__device)
227
+ # adapt the computing function
228
+ __compute = functools.partial(update_computation_state, model_obj=__model, tokenizer_obj=__tokenizer, device_str=__device)
229
+ # create the UI
230
+ __fields.update(create_layout(intro=intro))
231
+ # init the state
232
+ __fields.update(create_state())
233
+ # fetch the relevant fields
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'])
236
+ # wire the input fields
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']],
241
+ queue=False,
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')
261
+ # gradio application
262
+ return __app
263
+
264
+ # MAIN #########################################################################
265
+
266
+ if __name__ == '__main__':
267
+ __app = create_app()
268
+ __app.launch(share=True, debug=True)
@@ -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
  [tool.poetry]
2
2
  name = "psaiops"
3
- version = "0.0.10"
3
+ version = "0.0.11"
4
4
  description = "Web apps to inspect & engineer NN activations."
5
5
  license = ".github/LICENSE.md"
6
6
  readme = ".github/README.md"
@@ -1,155 +0,0 @@
1
- import functools
2
-
3
- import gradio
4
- import torch
5
- import torch.cuda
6
-
7
- import psaiops.score.attention.lib
8
-
9
- # META #########################################################################
10
-
11
- TITLE = '''Attention Scoring'''
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 = '''.white-text span { color: white; }'''
14
-
15
- MODEL = 'openai/gpt-oss-20b'
16
-
17
- # COLORS #######################################################################
18
-
19
- def create_color_map() -> dict:
20
- return {
21
- '-1': '#00ff00',
22
- **{str(__i): '#{:02x}0000'.format(int(2.55 * __i)) for __i in range(101)}}
23
-
24
- # INTRO ########################################################################
25
-
26
- def create_intro_block(intro: str) -> dict:
27
- __intro = gradio.Markdown(intro)
28
- return {'intro_block': __intro}
29
-
30
- # MODEL ########################################################################
31
-
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')
37
- return {
38
- 'model_block': __model_dd,
39
- 'layer_block': __layer_sl,
40
- 'head_block': __head_sl}
41
-
42
- # SAMPLING #####################################################################
43
-
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)
48
- return {
49
- 'tokens_block': __tokens,
50
- 'topk_block': __topk,
51
- 'topp_block': __topp}
52
-
53
- # DISPLAY ######################################################################
54
-
55
- def create_display_block() -> dict:
56
- __display = gradio.Radio(label='Display', value='Tokens', choices=['Tokens', 'Indexes'], interactive=True)
57
- return {'display_block': __display}
58
-
59
- # INPUTS #######################################################################
60
-
61
- 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)
63
- return {'input_block': __input}
64
-
65
- # OUTPUTS ######################################################################
66
-
67
- 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(), elem_classes='white-text')
69
- return {'output_block': __output}
70
-
71
- # ACTIONS ######################################################################
72
-
73
- 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'
76
- return {
77
- 'process_block': __process,
78
- 'position_block': __position}
79
-
80
- # STATE ########################################################################
81
-
82
- def create_state() -> dict:
83
- return {'attention_state': gradio.State(None), 'token_state': gradio.State(None)}
84
-
85
- # LAYOUT #######################################################################
86
-
87
- def create_layout(intro: str=INTRO) -> dict:
88
- __fields = {}
89
- __fields.update(create_intro_block(intro=intro))
90
- with gradio.Tabs():
91
- with gradio.Tab('Score Tokens') as __main_tab:
92
- __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():
99
- __fields.update(create_actions_block())
100
- with gradio.Tab('Settings') as __settings_tab:
101
- __fields.update({'settings_tab': __settings_tab})
102
- with gradio.Column(scale=1):
103
- with gradio.Row():
104
- __fields.update(create_model_block())
105
- with gradio.Row():
106
- __fields.update(create_sampling_block())
107
- with gradio.Row():
108
- __fields.update(create_display_block())
109
- return __fields
110
-
111
- # EVENTS #######################################################################
112
-
113
- def update_layer_range(value: int, model: str) -> dict:
114
- 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
-
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
123
-
124
- # APP ##########################################################################
125
-
126
- def create_app(title: str=TITLE, intro: str=INTRO, style: str=STYLE, model: str=MODEL) -> gradio.Blocks:
127
- __fields = {}
128
- with gradio.Blocks(theme=gradio.themes.Soft(), title=title, css=style) as __app:
129
- # load the model
130
- __device = 'cuda' if torch.cuda.is_available() else 'cpu'
131
- __model = psaiops.score.attention.lib.get_model(name=model, device=__device)
132
- __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)
135
- # create the UI
136
- __fields.update(create_layout(intro=intro))
137
- # init the state
138
- __fields.update(create_state())
139
- # fetch the relevant fields
140
- __button = __fields['process_block']
141
- # 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'],
146
- queue=False,
147
- show_progress='full')
148
- # gradio application
149
- return __app
150
-
151
- # MAIN #########################################################################
152
-
153
- if __name__ == '__main__':
154
- __app = create_app()
155
- __app.launch(share=True, debug=True)
File without changes
File without changes