psaiops 0.0.2__py3-none-any.whl → 0.0.3__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.

File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,142 @@
1
+ import gradio
2
+
3
+ import psaiops.score.attention.lib
4
+
5
+ # META #########################################################################
6
+
7
+ TITLE = '''Attention Scoring'''
8
+ INTRO = '''Score each token according to the weights of the attention layers.\nThe model is fixed to "openai/gpt-oss-20b" for now.'''
9
+ STYLE = ''''''
10
+
11
+ MODEL = 'openai/gpt-oss-20b'
12
+
13
+ # INTRO ########################################################################
14
+
15
+ def create_intro_block(intro: str) -> dict:
16
+ __intro = gradio.Markdown(intro)
17
+ return {'intro_block': __intro}
18
+
19
+ # MODEL ########################################################################
20
+
21
+ def create_model_block() -> dict:
22
+ __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'
23
+ __layer_sl = gradio.Slider(label='Layer Depth', value=12, minimum=-1, maximum=23, step=1, interactive=True) # info='-1 to average on all layers'
24
+ __head_sl = gradio.Slider(label='Attention Head', value=-1, minimum=-1, maximum=63, step=1, interactive=True) # info='-1 to average on all heads'
25
+ __model_dd.change(fn=update_layer_range, inputs=[__layer_sl, __model_dd], outputs=__layer_sl, queue=False, show_progress='hidden')
26
+ return {
27
+ 'model_block': __model_dd,
28
+ 'layer_block': __layer_sl,
29
+ 'head_block': __head_sl}
30
+
31
+ # SAMPLING #####################################################################
32
+
33
+ def create_sampling_block() -> dict:
34
+ __tokens = gradio.Slider(label='Tokens', value=32, minimum=0, maximum=128, step=1, interactive=True)
35
+ __topk = gradio.Slider(label='Top K', value=4, minimum=1, maximum=8, step=1, interactive=True)
36
+ __topp = gradio.Slider(label='Top P', value=0.8, minimum=0.0, maximum=1.0, step=0.1, interactive=True)
37
+ return {
38
+ 'tokens_block': __tokens,
39
+ 'topk_block': __topk,
40
+ 'topp_block': __topp}
41
+
42
+ # DISPLAY ######################################################################
43
+
44
+ def create_display_block() -> dict:
45
+ __display = gradio.Radio(label='Display', value='Tokens', choices=['Tokens', 'Indexes'], interactive=True)
46
+ return {'display_block': __display}
47
+
48
+ # INPUTS #######################################################################
49
+
50
+ def create_inputs_block() -> dict:
51
+ __input = gradio.Textbox(label='Prompt', value='', placeholder='A string of tokens to score.', lines=4, show_copy_button=True, interactive=True)
52
+ return {'input_block': __input}
53
+
54
+ # OUTPUTS ######################################################################
55
+
56
+ def create_outputs_block() -> dict:
57
+ __output = gradio.HighlightedText(label='Scores', value='', interactive=False, show_legend=False, show_inline_category=False, combine_adjacent=True)
58
+ return {'output_block': __output}
59
+
60
+ # ACTIONS ######################################################################
61
+
62
+ def create_actions_block() -> dict:
63
+ __process = gradio.Button('Process', variant='primary', size='lg', interactive=True)
64
+ __position = gradio.Slider(label='Position', value=-1, minimum=-1, maximum=128, step=1, interactive=True) # info='-1 to average on all tokens'
65
+ return {
66
+ 'process_block': __process,
67
+ 'position_block': __position}
68
+
69
+ # STATE ########################################################################
70
+
71
+ def create_state() -> dict:
72
+ return {'attention_state': gradio.State(None), 'token_state': gradio.State(None)}
73
+
74
+ # LAYOUT #######################################################################
75
+
76
+ def create_layout(intro: str=INTRO) -> dict:
77
+ __fields = {}
78
+ __fields.update(create_intro_block(intro=intro))
79
+ with gradio.Tabs():
80
+ with gradio.Tab('Score Tokens') as __main_tab:
81
+ __fields.update({'main_tab': __main_tab})
82
+ with gradio.Row():
83
+ with gradio.Column(scale=1):
84
+ __fields.update(create_inputs_block())
85
+ with gradio.Column(scale=1):
86
+ __fields.update(create_outputs_block())
87
+ with gradio.Row():
88
+ __fields.update(create_actions_block())
89
+ with gradio.Tab('Settings') as __settings_tab:
90
+ __fields.update({'settings_tab': __settings_tab})
91
+ with gradio.Column(scale=1):
92
+ with gradio.Row():
93
+ __fields.update(create_model_block())
94
+ with gradio.Row():
95
+ __fields.update(create_sampling_block())
96
+ with gradio.Row():
97
+ __fields.update(create_display_block())
98
+ return __fields
99
+
100
+ # EVENTS #######################################################################
101
+
102
+ def update_layer_range(value: int, model: str) -> dict:
103
+ return gradio.update(maximum=35, value=min(35, int(value))) if '120b' in model else gradio.update(maximum=23, value=min(23, int(value)))
104
+
105
+ def update_position_range(value: int, dimension: int) -> dict:
106
+ return gradio.update(maximum=dimension - 1, value=min(dimension - 1, value))
107
+
108
+ def update_output_value(
109
+ attention_data: torch.Tensor=None,
110
+ token_data: torch.Tensor=None,) -> torch.Tensor:
111
+ return
112
+
113
+ # APP ##########################################################################
114
+
115
+ def create_app(title: str=TITLE, intro: str=INTRO, style: str=STYLE, model: str=MODEL) -> gradio.Blocks:
116
+ __fields = {}
117
+ with gradio.Blocks(theme=gradio.themes.Soft(), title=title, css=style) as __app:
118
+ # init
119
+ __device = 'cuda' if torch.cuda.is_available() else 'cpu'
120
+ __model = psaiops.score.attention.lib.get_model(name=model, device=__device)
121
+ __tokenizer = psaiops.score.attention.lib.get_tokenizer(name=model, device=__device)
122
+ # create the UI
123
+ __fields.update(create_layout(intro=intro))
124
+ # init the state
125
+ __fields.update(create_state())
126
+ # fetch the relevant fields
127
+ __button = __fields['process_block']
128
+ # wire the input fields
129
+ __button.click(
130
+ fn=psaiops.score.attention.lib.score_tokens,
131
+ inputs=[__model, __tokenizer] + [__fields[__k] for __k in ['input_block', 'tokens_block', 'topk_block', 'topp_block', 'position_block', 'layer_block', 'head_block']] + [__device],
132
+ outputs=__fields['output_block'],
133
+ queue=False,
134
+ show_progress='full')
135
+ # gradio application
136
+ return __app
137
+
138
+ # MAIN #########################################################################
139
+
140
+ if __name__ == '__main__':
141
+ __app = create_app()
142
+ __app.launch(share=True, debug=True)
@@ -0,0 +1,191 @@
1
+ import functools
2
+
3
+ import torch
4
+ import transformers
5
+
6
+ import deformers.models.openai.gptoss
7
+
8
+ # LOAD #########################################################################
9
+
10
+ @functools.lru_cache(maxsize=4)
11
+ def get_tokenizer(name: str, device: str='cpu'):
12
+ return transformers.AutoTokenizer.from_pretrained(
13
+ name,
14
+ use_fast=True,
15
+ dtype='auto',
16
+ device_map=device)
17
+
18
+ @functools.lru_cache(maxsize=2)
19
+ def get_model(name: str, device: str='cpu'):
20
+ __model = deformers.models.openai.gptoss.GptOssForCausalInference.from_pretrained(
21
+ name,
22
+ dtype='auto',
23
+ device_map=device)
24
+ # toggle the inference mode (not training)
25
+ __model.eval()
26
+ # transformers model
27
+ return __model
28
+
29
+ # PREPROCESS #####################################################################
30
+
31
+ def preprocess_token_ids(
32
+ tokenizer_obj: object,
33
+ prompt_str: str,
34
+ device_str: str='cpu'
35
+ ) -> dict:
36
+ # tokenize
37
+ __inputs = tokenizer_obj(prompt_str, return_tensors='pt')
38
+ # move to the main device
39
+ return {__k: __v.to(device_str) for __k, __v in __inputs.items()}
40
+
41
+ # GENERATE #######################################################################
42
+
43
+ def generate_token_ids(
44
+ model_obj: object,
45
+ input_args: dict,
46
+ token_num: int,
47
+ topk_num: int = 4,
48
+ topp_num: float = 0.9,
49
+ ) -> torch.Tensor:
50
+ # generate completion
51
+ with torch.no_grad():
52
+ __outputs = model_obj.generate(
53
+ **input_args,
54
+ max_new_tokens=token_num,
55
+ do_sample=(0.0 < topp_num < 1.0) or (topk_num > 0),
56
+ top_k=topk_num if (topk_num > 0) else None,
57
+ top_p=topp_num if (0.0 < topp_num < 1.0) else None,
58
+ return_dict_in_generate=True,
59
+ output_hidden_states=False,
60
+ output_attentions=False,
61
+ output_scores=False,
62
+ early_stopping=True,
63
+ use_cache=True)
64
+ # full sequence
65
+ return __outputs.sequences # (1, T)
66
+
67
+ # COMPUTE ########################################################################
68
+
69
+ def compute_attention_weights(
70
+ model_obj: object,
71
+ token_obj: torch.Tensor,
72
+ ) -> torch.Tensor:
73
+ # process the full sequence
74
+ with torch.no_grad():
75
+ __outputs = model_obj(
76
+ input_ids=token_obj,
77
+ output_attentions=True,
78
+ return_dict=True)
79
+ # parse the outputs
80
+ return torch.stack(__outputs.attentions, dim=0)
81
+
82
+ # REDUCE #######################################################################
83
+
84
+ def reduce_attention_weights(
85
+ attention_data: torch.Tensor,
86
+ token_idx: int, # -1 => avg over all tokens
87
+ layer_idx: int, # -1 => avg over layers
88
+ head_idx: int, # -1 => avg over heads
89
+ input_dim: int,
90
+ ) -> torch.Tensor:
91
+ # parse
92
+ __layer_dim, __batch_dim, __head_dim, __output_dim, __output_dim = tuple(attention_data.shape) # L, B, H, T, T
93
+ __layer_idx = min(layer_idx, __layer_dim)
94
+ __head_idx = min(head_idx, __head_dim)
95
+ __token_idx = min(token_idx, __output_dim - input_dim - 1) # T = I + O
96
+ # select the relevant data along each axis
97
+ __layer_slice = slice(None) if (__layer_idx < 0) else slice(__layer_idx, __layer_idx + 1)
98
+ __sample_slice = slice(None)
99
+ __head_slice = slice(None) if (__head_idx < 0) else slice(__head_idx, __head_idx + 1)
100
+ __token_slice = slice(input_dim, __output_dim) if (__token_idx < 0) else slice(input_dim + __token_idx, input_dim + __token_idx + 1)
101
+ # filter the data
102
+ __data = attention_data[__layer_slice, __sample_slice, __head_slice, __token_slice, slice(None)]
103
+ # reduce all the axes but the last
104
+ return __data.mean(dim=tuple(range(len(__data.shape) - 1)))
105
+
106
+ # FORMAT #########################################################################
107
+
108
+ def postprocess_attention_scores(
109
+ attention_data: torch.Tensor, # (T,)
110
+ input_dim: int,
111
+ token_idx: int,
112
+ ) -> list:
113
+ __output_dim = int(attention_data.shape[-1])
114
+ # isolate the scores of the input prompt
115
+ __input_slice = slice(0, input_dim)
116
+ # mask the token that were used to compute the scores
117
+ __token_idx = min(token_idx, __output_dim - input_dim - 1) # T = I + O
118
+ __output_range = list(range(__output_dim - input_dim)) if (__token_idx < 0) else [__token_idx]
119
+ __output_mask = torch.BoolTensor([__i in __output_range for __i in range(__output_dim - input_dim)])
120
+ # normalize the scores
121
+ __input_scores = attention_data[__input_slice] / (attention_data[__input_slice].sum() + 1e-5)
122
+ # round to obtain integer labels from 0 to 100
123
+ __input_scores = torch.round(100.0 * __input_scores, decimals=0).type(torch.int32)
124
+ # the generated tokens are not scored
125
+ __output_scores = torch.where(__output_mask, -1, 0).type(torch.int32)
126
+ # native list of integers
127
+ return __input_scores.tolist() + __output_scores.tolist() # (I,) + (O,) = (T,)
128
+
129
+ # POSTPROCESS ####################################################################
130
+
131
+ def postprocess_token_ids(
132
+ tokenizer_obj: object,
133
+ token_obj: torch.Tensor,
134
+ ) -> list:
135
+ # remove the batch axis
136
+ __indices = token_obj.squeeze().tolist()
137
+ # back to token strings
138
+ __tokens = tokenizer_obj.convert_ids_to_tokens(__indices)
139
+ # normalize the tokens
140
+ return [__t.replace('Ġ', ' ') for __t in __tokens]
141
+
142
+ # COMPUTE ########################################################################
143
+
144
+ def score_tokens(
145
+ model_obj: object,
146
+ tokenizer_obj: object,
147
+ prompt_str: str,
148
+ token_num: int=32,
149
+ topk_num: int = 4,
150
+ topp_num: float = 0.9,
151
+ token_idx: int, # -1 => avg over all tokens
152
+ layer_idx: int, # -1 => avg over layers
153
+ head_idx: int, # -1 => avg over heads
154
+ device_str: str='cuda',
155
+ ) -> list:
156
+ # dictionary {'input_ids': _, 'attention_mask': _}
157
+ __inputs = preprocess_token_ids(
158
+ tokenizer_obj=tokenizer_obj,
159
+ prompt_str=prompt_str,
160
+ device_str=device_str)
161
+ # parse the inputs
162
+ __input_dim = int(__inputs['input_ids'].shape[-1])
163
+ # tensor (1, T)
164
+ __outputs = generate_token_ids(
165
+ model_obj=model_obj,
166
+ input_args=__inputs,
167
+ token_num=token_num,
168
+ topk_num=topk_num,
169
+ topp_num=topp_num)
170
+ # tensor (L, S, H, T, T)
171
+ __attentions = compute_attention_weights(
172
+ model_obj=model_obj,
173
+ token_obj=__outputs)
174
+ # reduce the layer, sample, head and output token axes => tensor (T,)
175
+ __scores = reduce_attention_weights(
176
+ __attentions,
177
+ token_idx=token_idx,
178
+ layer_idx=layer_idx,
179
+ head_idx=head_idx,
180
+ input_dim=__input_dim)
181
+ # translate the scores into integer labels
182
+ __labels = postprocess_attention_scores(
183
+ __scores,
184
+ input_dim=__input_dim,
185
+ token_idx=token_idx)
186
+ # detokenize the IDs
187
+ __tokens = postprocess_token_ids(
188
+ tokenizer_obj=__tokenizer,
189
+ token_obj=__outputs)
190
+ # match tokens and labels for the HighlightedText field
191
+ return list(zip(__tokens, __labels))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: psaiops
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Web apps to inspect & engineer NN activations.
5
5
  License: .github/LICENSE.md
6
6
  Author: apehex
@@ -0,0 +1,15 @@
1
+ psaiops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ psaiops/combine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ psaiops/compose/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ psaiops/compose/contrast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ psaiops/edit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ psaiops/elements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ psaiops/elements/data.py,sha256=vGYeMN11uP9gs8rV6aSDffE_TeIX5PmdzWGwUpdGE2Y,906
8
+ psaiops/score/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ psaiops/score/attention/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ psaiops/score/attention/app.py,sha256=FgTyGrklLfWVQICwtXT7mohBXmSMVyv5iRSgNC64Z-0,6549
11
+ psaiops/score/attention/lib.py,sha256=UQObfalIAenLdg3qZw5l003fenvB5RLeav4G-8H3RHs,6925
12
+ psaiops/steer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ psaiops-0.0.3.dist-info/METADATA,sha256=aof38JNXN2bi0cG31ba1JCLfrB6onSCd3R-econzaL0,1221
14
+ psaiops-0.0.3.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
15
+ psaiops-0.0.3.dist-info/RECORD,,
psaiops/score/app.py DELETED
@@ -1,27 +0,0 @@
1
- import gradio
2
-
3
- # META #########################################################################
4
-
5
- TITLE = '''Token Scoring'''
6
- INTRO = '''Score each input / output token according to a given metric.'''
7
- STYLE = ''''''
8
-
9
- # MODEL ########################################################################
10
-
11
- def create_model_tab() -> None:
12
- pass
13
-
14
- # ROOT #########################################################################
15
-
16
- def create_root_block(title: str=TITLE, intro: str=INTRO, style: str=STYLE) -> gradio.Block:
17
- with gradio.Blocks(theme=gradio.themes.Soft(), title=title, css=style) as __app:
18
- with gradio.Row():
19
- with gradio.Column(scale=1):
20
- gradio.Markdown(intro)
21
- return __app
22
-
23
- # MAIN #########################################################################
24
-
25
- if __name__ == '__main__':
26
- __app = create_root_block()
27
- __app.launch(share=True, debug=True)
@@ -1,10 +0,0 @@
1
- psaiops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- psaiops/compose/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- psaiops/elements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- psaiops/elements/data.py,sha256=vGYeMN11uP9gs8rV6aSDffE_TeIX5PmdzWGwUpdGE2Y,906
5
- psaiops/score/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- psaiops/score/app.py,sha256=7HfVEFdzJ1IarJ7A4FaWR8GeeGw2tttG993Qn-gzBmY,910
7
- psaiops/steer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- psaiops-0.0.2.dist-info/METADATA,sha256=Xu81-r81hohISmebG13Cftrr5tyETxei_Jar8ZxWCZM,1221
9
- psaiops-0.0.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
10
- psaiops-0.0.2.dist-info/RECORD,,