treescope 0.1.0__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.
Files changed (55) hide show
  1. treescope/__init__.py +171 -0
  2. treescope/_internal/__init__.py +13 -0
  3. treescope/_internal/api/__init__.py +13 -0
  4. treescope/_internal/api/array_autovisualizer.py +397 -0
  5. treescope/_internal/api/arrayviz.py +1034 -0
  6. treescope/_internal/api/autovisualize.py +128 -0
  7. treescope/_internal/api/default_renderer.py +209 -0
  8. treescope/_internal/api/ipython_integration.py +417 -0
  9. treescope/_internal/arrayviz_impl.py +708 -0
  10. treescope/_internal/docs_util.py +25 -0
  11. treescope/_internal/figures_impl.py +102 -0
  12. treescope/_internal/handlers/__init__.py +15 -0
  13. treescope/_internal/handlers/autovisualizer_hook.py +181 -0
  14. treescope/_internal/handlers/basic_types_handler.py +456 -0
  15. treescope/_internal/handlers/canonical_alias_postprocessor.py +124 -0
  16. treescope/_internal/handlers/custom_type_handlers.py +102 -0
  17. treescope/_internal/handlers/function_reflection_handlers.py +134 -0
  18. treescope/_internal/handlers/generic_pytree_handler.py +90 -0
  19. treescope/_internal/handlers/generic_repr_handler.py +117 -0
  20. treescope/_internal/handlers/repr_html_postprocessor.py +120 -0
  21. treescope/_internal/handlers/shared_value_postprocessor.py +351 -0
  22. treescope/_internal/html_encapsulation.py +385 -0
  23. treescope/_internal/html_escaping.py +50 -0
  24. treescope/_internal/js/arrayviz.js +1291 -0
  25. treescope/_internal/object_inspection.py +77 -0
  26. treescope/_internal/parts/__init__.py +22 -0
  27. treescope/_internal/parts/basic_parts.py +1620 -0
  28. treescope/_internal/parts/common_structures.py +280 -0
  29. treescope/_internal/parts/common_styles.py +573 -0
  30. treescope/_internal/parts/custom_dataclass_util.py +163 -0
  31. treescope/_internal/parts/embedded_iframe.py +174 -0
  32. treescope/_internal/parts/foldable_impl.py +460 -0
  33. treescope/_internal/parts/part_interface.py +379 -0
  34. treescope/canonical_aliases.py +557 -0
  35. treescope/context.py +174 -0
  36. treescope/dataclass_util.py +105 -0
  37. treescope/dtype_util.py +89 -0
  38. treescope/external/__init__.py +21 -0
  39. treescope/external/jax_support.py +606 -0
  40. treescope/external/numpy_support.py +314 -0
  41. treescope/external/torch_support.py +532 -0
  42. treescope/figures.py +262 -0
  43. treescope/formatting_util.py +110 -0
  44. treescope/handlers.py +66 -0
  45. treescope/layout_algorithms.py +244 -0
  46. treescope/lowering.py +444 -0
  47. treescope/ndarray_adapters.py +296 -0
  48. treescope/renderers.py +415 -0
  49. treescope/rendering_parts.py +115 -0
  50. treescope/repr_lib.py +308 -0
  51. treescope/type_registries.py +251 -0
  52. treescope-0.1.0.dist-info/LICENSE +202 -0
  53. treescope-0.1.0.dist-info/METADATA +148 -0
  54. treescope-0.1.0.dist-info/RECORD +55 -0
  55. treescope-0.1.0.dist-info/WHEEL +4 -0
treescope/__init__.py ADDED
@@ -0,0 +1,171 @@
1
+ # Copyright 2024 The Treescope Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Treescope: An interactive HTML pretty-printer and array visualizer.
16
+
17
+ You can configure treescope as the default IPython pretty-printer using ::
18
+
19
+ treescope.basic_interactive_setup()
20
+
21
+ or, for more control: ::
22
+
23
+ treescope.register_as_default()
24
+ treescope.register_autovisualize_magic()
25
+ treescope.active_autovisualizer.set_globally(
26
+ treescope.ArrayAutovisualizer()
27
+ )
28
+
29
+ You can also pretty-print individual values using `treescope.show` or
30
+ `treescope.display`.
31
+ """
32
+ from __future__ import annotations
33
+
34
+ # pylint: disable=g-importing-member,g-multiple-import,unused-import
35
+
36
+ import typing
37
+
38
+ from . import _internal
39
+ from . import canonical_aliases
40
+ from . import context
41
+ from . import dataclass_util
42
+ from . import figures
43
+ from . import formatting_util
44
+ from . import handlers
45
+ from . import lowering
46
+ from . import ndarray_adapters
47
+ from . import renderers
48
+ from . import rendering_parts
49
+ from . import repr_lib
50
+ from . import type_registries
51
+
52
+ from ._internal.api.array_autovisualizer import (
53
+ ArrayAutovisualizer,
54
+ )
55
+ from ._internal.api.arrayviz import (
56
+ default_diverging_colormap,
57
+ default_sequential_colormap,
58
+ integer_digitbox,
59
+ render_array,
60
+ render_array_sharding,
61
+ )
62
+ from ._internal.api.autovisualize import (
63
+ IPythonVisualization,
64
+ VisualizationFromTreescopePart,
65
+ ChildAutovisualizer,
66
+ Autovisualizer,
67
+ active_autovisualizer,
68
+ )
69
+ from ._internal.api.default_renderer import (
70
+ active_renderer,
71
+ active_expansion_strategy,
72
+ render_to_html,
73
+ render_to_text,
74
+ using_expansion_strategy,
75
+ )
76
+ from ._internal.api.ipython_integration import (
77
+ default_magic_autovisualizer,
78
+ basic_interactive_setup,
79
+ display,
80
+ register_as_default,
81
+ register_autovisualize_magic,
82
+ register_context_manager_magic,
83
+ show,
84
+ )
85
+
86
+ if typing.TYPE_CHECKING:
87
+ Callable = typing.Callable
88
+
89
+ # Type annotations and docstrings for imported constants. Used by sphinx to
90
+ # generate documentation.
91
+
92
+ default_diverging_colormap: context.ContextualValue[
93
+ list[tuple[int, int, int]]
94
+ ] = default_diverging_colormap
95
+ """Default diverging colormap.
96
+
97
+ Used by `render_array` when ``around_zero`` is True. Intended for user
98
+ customization in an interactive setting.
99
+ """
100
+
101
+ default_sequential_colormap: context.ContextualValue[
102
+ list[tuple[int, int, int]]
103
+ ] = default_sequential_colormap
104
+ """Default sequential colormap.
105
+
106
+ Used by `render_array` when ``around_zero`` is False. Intended for user
107
+ customization in an interactive setting.
108
+ """
109
+
110
+ active_autovisualizer: context.ContextualValue[Autovisualizer | None] = (
111
+ active_autovisualizer
112
+ )
113
+ """The active autovisualizer to use when rendering a tree to HTML.
114
+
115
+ This can be overridden interactively to enable rich visualizations in
116
+ treescope. Users are free to set this to an arbitrary renderer of their
117
+ choice; a common choice is arrayviz's `ArrayAutovisualizer()`.
118
+ """
119
+
120
+ active_renderer: context.ContextualValue[renderers.TreescopeRenderer] = (
121
+ active_renderer
122
+ )
123
+ """The default renderer to use when rendering a tree to HTML.
124
+
125
+ This determines the set of handlers and postprocessors to use when
126
+ rendering an object.
127
+
128
+ This can be overridden locally to reconfigure how nodes are rendered
129
+ with treescope. Users are free to set this to an arbitrary renderer of
130
+ their choice, and programs should not assume the renderer has a
131
+ particular form. Library functions can retrieve the current value of
132
+ this to render objects in a user-configurable way, and can optionally
133
+ make further adjustments using `TreescopeRenderer.extend_with`.
134
+ """
135
+
136
+ active_expansion_strategy: context.ContextualValue[
137
+ Callable[[rendering_parts.RenderableTreePart], None]
138
+ ] = active_expansion_strategy
139
+ """The default expansion strategy to use when rendering a tree to HTML.
140
+
141
+ Expansion strategies are used to figure out how deeply to unfold an object
142
+ by default. They should operate by setting the expand states of the
143
+ foldable nodes inside the object.
144
+ """
145
+
146
+ # Package version.
147
+ __version__ = '0.1.0'
148
+
149
+ default_magic_autovisualizer: context.ContextualValue[Autovisualizer] = (
150
+ default_magic_autovisualizer
151
+ )
152
+ """The default autovisualizer to use for the ``%%autovisualize`` magic.
153
+
154
+ This can be overridden interactively to customize the autovisualizer
155
+ used by ``%%autovisualize``.
156
+ """
157
+
158
+
159
+ # Set up canonical aliases for the treescope API itself.
160
+ def _setup_canonical_aliases_for_api():
161
+ import types # pylint: disable=g-import-not-at-top
162
+
163
+ for key, value in globals().items():
164
+ if isinstance(value, types.FunctionType) or isinstance(value, type):
165
+ canonical_aliases.add_alias(
166
+ value, canonical_aliases.ModuleAttributePath(__name__, (key,))
167
+ )
168
+
169
+
170
+ _setup_canonical_aliases_for_api()
171
+ del _setup_canonical_aliases_for_api, typing
@@ -0,0 +1,13 @@
1
+ # Copyright 2024 The Treescope Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,13 @@
1
+ # Copyright 2024 The Treescope Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,397 @@
1
+ # Copyright 2024 The Treescope Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """An automatic NDArray visualizer using arrayviz."""
16
+ from __future__ import annotations
17
+
18
+ import dataclasses
19
+ import sys
20
+ from typing import Any, Callable, Collection
21
+
22
+ import numpy as np
23
+ from treescope import dtype_util
24
+ from treescope import lowering
25
+ from treescope import ndarray_adapters
26
+ from treescope import rendering_parts
27
+ from treescope import type_registries
28
+ from treescope._internal import arrayviz_impl
29
+ from treescope._internal.api import arrayviz
30
+ from treescope._internal.api import autovisualize
31
+
32
+
33
+ PositionalAxisInfo = ndarray_adapters.PositionalAxisInfo
34
+ NamedPositionlessAxisInfo = ndarray_adapters.NamedPositionlessAxisInfo
35
+ NamedPositionalAxisInfo = ndarray_adapters.NamedPositionalAxisInfo
36
+ AxisInfo = ndarray_adapters.AxisInfo
37
+
38
+ ArrayInRegistry = Any
39
+
40
+
41
+ def _supported_dtype(dtype: np.dtype | None):
42
+ return dtype is not None and (
43
+ dtype_util.is_integer_dtype(dtype)
44
+ or dtype_util.is_floating_dtype(dtype)
45
+ or np.issubdtype(dtype, np.bool_)
46
+ )
47
+
48
+
49
+ @dataclasses.dataclass
50
+ class ArrayAutovisualizer:
51
+ """An automatic visualizer for arrays.
52
+
53
+ ArrayAutovisualizer supports any array type registered with an NDArrayAdapter
54
+ in the global type registry.
55
+
56
+ Attributes:
57
+ maximum_size: Maximum numer of elements of an array to show. Arrays larger
58
+ than this will be truncated along one or more axes.
59
+ cutoff_size_per_axis: Maximum number of elements of each individual axis to
60
+ show without truncation. Any axis longer than this will be truncated, with
61
+ their visual size increasing logarithmically with the true axis size
62
+ beyond this point.
63
+ edge_items: How many values to keep along each axis for truncated arrays.
64
+ prefers_column: Names that should always be assigned to columns.
65
+ prefers_row: Names that should always be assigned to rows.
66
+ around_zero: Whether to center continous data around zero.
67
+ force_continuous: Whether to always render integer arrays as continuous.
68
+ include_repr_line_threshold: A threshold such that, if the `repr` of the
69
+ array has fewer than that many lines, we will include that repr in the
70
+ visualization. Useful for seeing small array values.
71
+ token_lookup_fn: Optional function that looks up token IDs and adds them to
72
+ the visualization on hover.
73
+ """
74
+
75
+ maximum_size: int = 4_000
76
+ cutoff_size_per_axis: int = 128
77
+ edge_items: int = 5
78
+ prefers_column: Collection[str] = ()
79
+ prefers_row: Collection[str] = ()
80
+ around_zero: bool = True
81
+ force_continuous: bool = False
82
+ include_repr_line_threshold: int = 5
83
+ token_lookup_fn: Callable[[int], str] | None = None
84
+
85
+ def _autovisualize_array(
86
+ self,
87
+ array: ArrayInRegistry,
88
+ adapter: ndarray_adapters.NDArrayAdapter,
89
+ path: str | None,
90
+ label: str,
91
+ expand_state: rendering_parts.ExpandState,
92
+ ) -> rendering_parts.RenderableTreePart:
93
+ """Helper to visualize an array."""
94
+ # Extract information about axis names, indices, and sizes.
95
+ array_axis_info = adapter.get_axis_info_for_array_data(array)
96
+
97
+ # Assign axes, using preferred axes if possible.
98
+ row_axes = []
99
+ column_axes = []
100
+ for info in array_axis_info:
101
+ if isinstance(info, NamedPositionalAxisInfo | NamedPositionlessAxisInfo):
102
+ if info.axis_name in self.prefers_column:
103
+ column_axes.append(info.axis_name)
104
+ elif info.axis_name in self.prefers_row:
105
+ row_axes.append(info.axis_name)
106
+
107
+ # Infer a good truncated shape for this array.
108
+ edge_items_per_axis = arrayviz_impl.infer_balanced_truncation(
109
+ tuple(info.size for info in array_axis_info),
110
+ maximum_size=self.maximum_size,
111
+ cutoff_size_per_axis=self.cutoff_size_per_axis,
112
+ minimum_edge_items=self.edge_items,
113
+ )
114
+
115
+ row_axes, column_axes = arrayviz_impl.infer_rows_and_columns(
116
+ all_axes=array_axis_info,
117
+ known_rows=row_axes,
118
+ known_columns=column_axes,
119
+ edge_items_per_axis=edge_items_per_axis,
120
+ )
121
+
122
+ # Obtain truncated array and mask data from the adapter.
123
+ truncated_array_data, truncated_mask_data = (
124
+ adapter.get_array_data_with_truncation(
125
+ array=array,
126
+ mask=None,
127
+ edge_items_per_axis=edge_items_per_axis,
128
+ )
129
+ )
130
+
131
+ # Maybe infer value labels from a tokenizer.
132
+ if (
133
+ self.token_lookup_fn
134
+ and not self.force_continuous
135
+ and dtype_util.is_integer_dtype(truncated_array_data.dtype)
136
+ ):
137
+ tokens = np.unique(truncated_array_data.flatten()).tolist()
138
+ value_item_labels = {
139
+ token: self.token_lookup_fn(token) for token in tokens
140
+ }
141
+ else:
142
+ value_item_labels = None
143
+
144
+ array_rendering = arrayviz._render_pretruncated( # pylint: disable=protected-access
145
+ array_axis_info=array_axis_info,
146
+ row_infos=row_axes,
147
+ column_infos=column_axes,
148
+ slider_infos=(),
149
+ truncated_array_data=truncated_array_data,
150
+ truncated_mask_data=truncated_mask_data,
151
+ edge_items_per_axis=edge_items_per_axis,
152
+ continuous="auto",
153
+ around_zero=self.around_zero,
154
+ vmax=None,
155
+ vmin=None,
156
+ trim_outliers=True,
157
+ dynamic_colormap="auto",
158
+ colormap=None,
159
+ axis_item_labels=None,
160
+ value_item_labels=value_item_labels,
161
+ axis_labels=None,
162
+ )
163
+ outputs = [array_rendering]
164
+ last_line_parts = []
165
+
166
+ # Render the sharding as well.
167
+ sharding_info = adapter.get_sharding_info_for_array_data(array)
168
+ if sharding_info:
169
+ num_devices = len(sharding_info.device_index_to_shard_slices)
170
+ if num_devices == 1:
171
+ [device_id] = sharding_info.device_index_to_shard_slices.keys()
172
+ last_line_parts.append(
173
+ f"| Device: {sharding_info.device_type} {device_id}"
174
+ )
175
+ else:
176
+ if sharding_info.fully_replicated:
177
+ sharding_summary_str = (
178
+ "Replicated across"
179
+ f" {num_devices} {sharding_info.device_type} devices"
180
+ )
181
+ else:
182
+ sharding_summary_str = (
183
+ "Sharded across"
184
+ f" {num_devices} {sharding_info.device_type} devices"
185
+ )
186
+ sharding_rendering = arrayviz.render_array_sharding(
187
+ array,
188
+ columns=[c.logical_key() for c in column_axes],
189
+ rows=[r.logical_key() for r in row_axes],
190
+ ).treescope_part
191
+ outputs.append(
192
+ rendering_parts.build_custom_foldable_tree_node(
193
+ label=rendering_parts.abbreviation_color(
194
+ rendering_parts.siblings(
195
+ rendering_parts.text(sharding_summary_str),
196
+ rendering_parts.fold_condition(
197
+ expanded=rendering_parts.text(":"),
198
+ collapsed=rendering_parts.text(
199
+ " (click to expand)"
200
+ ),
201
+ ),
202
+ )
203
+ ),
204
+ contents=rendering_parts.fold_condition(
205
+ expanded=rendering_parts.indented_children(
206
+ [sharding_rendering]
207
+ ),
208
+ ),
209
+ )
210
+ )
211
+
212
+ # We render it with a path, but remove the copy path button. This will be
213
+ # added back by the caller.
214
+ if last_line_parts:
215
+ last_line = rendering_parts.siblings(
216
+ rendering_parts.fold_condition(
217
+ expanded=rendering_parts.text("".join(last_line_parts)),
218
+ ),
219
+ rendering_parts.text(">"),
220
+ )
221
+ else:
222
+ last_line = rendering_parts.text(">")
223
+ custom_rendering = rendering_parts.build_custom_foldable_tree_node(
224
+ label=rendering_parts.abbreviation_color(label),
225
+ contents=rendering_parts.siblings(
226
+ rendering_parts.fold_condition(
227
+ expanded=rendering_parts.indented_children(outputs)
228
+ ),
229
+ rendering_parts.abbreviation_color(last_line),
230
+ ),
231
+ path=path,
232
+ expand_state=expand_state,
233
+ )
234
+ return custom_rendering.renderable
235
+
236
+ def __call__(
237
+ self, value: Any, path: str | None
238
+ ) -> autovisualize.VisualizationFromTreescopePart | None:
239
+ """Implementation of an autovisualizer, visualizing arrays."""
240
+ # Retrieve the adapter for this array, which we will use to construct
241
+ # the rendering.
242
+ adapter = type_registries.lookup_ndarray_adapter(value)
243
+ if adapter is not None:
244
+ if not adapter.should_autovisualize(value):
245
+ return None
246
+
247
+ # This is an array we can visualize!
248
+ # Extract information about axis names, indices, and sizes.
249
+ array_axis_info = adapter.get_axis_info_for_array_data(value)
250
+ total_size = np.prod([ax.size for ax in array_axis_info])
251
+ if total_size == 1:
252
+ # Don't visualize scalars.
253
+ return None
254
+
255
+ np_dtype = adapter.get_numpy_dtype(value)
256
+ if not _supported_dtype(np_dtype):
257
+ return None
258
+
259
+ def _placeholder() -> rendering_parts.RenderableTreePart:
260
+ summary = adapter.get_array_summary(value, fast=True)
261
+ return rendering_parts.deferred_placeholder_style(
262
+ rendering_parts.text(f"<{summary}>")
263
+ )
264
+
265
+ def _thunk(
266
+ expand_state: rendering_parts.ExpandState | None,
267
+ ) -> rendering_parts.RenderableTreePart:
268
+ # Full rendering of the array.
269
+ if expand_state is None:
270
+ expand_state = rendering_parts.ExpandState.WEAKLY_EXPANDED
271
+ summary = adapter.get_array_summary(value, fast=False)
272
+ label = rendering_parts.abbreviation_color(
273
+ rendering_parts.text(f"<{summary}")
274
+ )
275
+ return self._autovisualize_array(
276
+ value, adapter, path, label, expand_state
277
+ )
278
+
279
+ return autovisualize.VisualizationFromTreescopePart(
280
+ rendering_parts.RenderableAndLineAnnotations(
281
+ renderable=lowering.maybe_defer_rendering(
282
+ _thunk, _placeholder, expanded_newlines_for_layout=8
283
+ ),
284
+ annotations=rendering_parts.build_copy_button(path),
285
+ )
286
+ )
287
+
288
+ # Not an array in the registry. But it might be a JAX sharding that we can
289
+ # visualize (if JAX is imported).
290
+ if "jax" in sys.modules:
291
+ import jax # pylint: disable=g-import-not-at-top
292
+
293
+ if isinstance(
294
+ value,
295
+ jax.sharding.PositionalSharding
296
+ | jax.sharding.NamedSharding
297
+ | jax.sharding.Mesh,
298
+ ):
299
+ raw_repr = repr(value)
300
+ repr_oneline = " ".join(line.strip() for line in raw_repr.split("\n"))
301
+
302
+ if isinstance(value, jax.sharding.PositionalSharding):
303
+ sharding = value
304
+ fake_axis_info = [
305
+ PositionalAxisInfo(i, size)
306
+ for i, size in enumerate(sharding.shape)
307
+ ]
308
+ elif isinstance(value, jax.sharding.NamedSharding):
309
+ sharding = value
310
+ # Named shardings still act on positional arrays, so show them for
311
+ # the positional shape they require.
312
+ fake_sizes = []
313
+ for part in value.spec:
314
+ if part is None:
315
+ fake_sizes.append(1)
316
+ elif isinstance(part, str):
317
+ fake_sizes.append(value.mesh.shape[part])
318
+ else:
319
+ size = int(np.prod([value.mesh.shape[a] for a in part]))
320
+ fake_sizes.append(size)
321
+ fake_axis_info = [
322
+ PositionalAxisInfo(i, size) for i, size in enumerate(fake_sizes)
323
+ ]
324
+ else:
325
+ # Meshes are based on named axes. We build a temporary positional
326
+ # sharding for visualization, but keep track of name order.
327
+ assert isinstance(value, jax.sharding.Mesh)
328
+ mesh = value
329
+ sharding = jax.sharding.NamedSharding(
330
+ value, jax.sharding.PartitionSpec(*mesh.axis_names)
331
+ )
332
+ fake_axis_info = [
333
+ NamedPositionlessAxisInfo(name, mesh.shape[name])
334
+ for name in mesh.axis_names
335
+ ]
336
+
337
+ fake_shape = tuple(ax.size for ax in fake_axis_info)
338
+ some_device = next(iter(sharding.device_set))
339
+ device_index_map = sharding.devices_indices_map(fake_shape)
340
+ sharding_info = ndarray_adapters.ShardingInfo(
341
+ shard_shape=sharding.shard_shape(fake_shape),
342
+ device_index_to_shard_slices={
343
+ d.id: v for d, v in device_index_map.items()
344
+ },
345
+ device_type=some_device.platform.upper(),
346
+ fully_replicated=sharding.is_fully_replicated,
347
+ )
348
+ shardvis = arrayviz.render_sharding_info(
349
+ array_axis_info=fake_axis_info,
350
+ sharding_info=sharding_info,
351
+ ).treescope_part
352
+ custom_rendering = rendering_parts.build_custom_foldable_tree_node(
353
+ label=rendering_parts.abbreviation_color(
354
+ rendering_parts.text("<" + repr_oneline)
355
+ ),
356
+ contents=rendering_parts.siblings(
357
+ rendering_parts.fold_condition(
358
+ expanded=rendering_parts.indented_children([shardvis])
359
+ ),
360
+ rendering_parts.abbreviation_color(rendering_parts.text(">")),
361
+ ),
362
+ path=path,
363
+ expand_state=rendering_parts.ExpandState.EXPANDED,
364
+ )
365
+ return autovisualize.VisualizationFromTreescopePart(custom_rendering)
366
+ else:
367
+ return None
368
+
369
+ @classmethod
370
+ def for_tokenizer(cls, tokenizer: Any):
371
+ """Builds an autovisualizer for a tokenizer.
372
+
373
+ This method constructs an ArrayAutovisualizer that annotates integer array
374
+ elements with their token strings. This can then be used to autovisualize
375
+ tokenized arrays.
376
+
377
+ Args:
378
+ tokenizer: A tokenizer to use. Either a callable mapping token IDs to
379
+ strings, or a SentencePieceProcessor.
380
+
381
+ Returns:
382
+ An ArrayAutovisualizer that annotates integer array elements with their
383
+ token strings.
384
+ """
385
+ if callable(tokenizer):
386
+ return cls(token_lookup_fn=lambda x: repr(tokenizer(x)))
387
+ elif hasattr(tokenizer, "IdToPiece") and hasattr(tokenizer, "GetPieceSize"):
388
+
389
+ def lookup(x):
390
+ if x >= 0 and x < tokenizer.GetPieceSize():
391
+ return repr(tokenizer.IdToPiece(x))
392
+ else:
393
+ return f"<out of bounds: {x}>"
394
+
395
+ return cls(token_lookup_fn=lookup)
396
+ else:
397
+ raise ValueError(f"Unknown tokenizer type: {tokenizer}")